Working blocker
This commit is contained in:
@@ -470,7 +470,7 @@ change ~org-directory~. It must be set before org loads!
|
||||
(org-agenda-start-day "2026-01-29")
|
||||
(org-agenda-overriding-header "📅 Installation Bombardier")
|
||||
(org-agenda-prefix-format " %12t") ;; reserve time space
|
||||
(org-agenda-todo-keyword-format " %-12s ") ;; fixed-width TODO
|
||||
(org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO
|
||||
(org-agenda-tags-column -100) ;; right-align tags
|
||||
(org-agenda-time-grid nil)
|
||||
)))
|
||||
@@ -2248,6 +2248,29 @@ If FILE is nil, refile in the current file."
|
||||
(nthcdr 3 (decode-time next)))))))
|
||||
(t t1))))
|
||||
|
||||
(defun gortium/org--get-range-end (pos)
|
||||
"Extract the END timestamp from an existing range like <A>--<B> at POS.
|
||||
POS can be a marker or a cons cell (file . position)."
|
||||
(let ((marker (if (markerp pos) pos
|
||||
(let ((file (car pos))
|
||||
(p (cdr pos)))
|
||||
(with-current-buffer (find-file-noselect file)
|
||||
(copy-marker p))))))
|
||||
(with-current-buffer (marker-buffer marker)
|
||||
(save-excursion
|
||||
(goto-char marker)
|
||||
(org-back-to-heading t)
|
||||
(let ((subtree-end (save-excursion (org-end-of-subtree t) (point)))
|
||||
(end-time nil))
|
||||
(save-restriction
|
||||
(narrow-to-region (point) subtree-end)
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "<[^>]+>--<\\([^>]+\\)>" nil t)
|
||||
(condition-case nil
|
||||
(setq end-time (org-time-string-to-time (match-string 1)))
|
||||
(error nil))))
|
||||
end-time)))))
|
||||
|
||||
(defun gortium/org--get-range-start (pos)
|
||||
"Extract the start timestamp from an existing range like <A>--<B>.
|
||||
Returns nil if no range found (safe, non-blocking)."
|
||||
@@ -2321,7 +2344,7 @@ Returns nil if no range found (safe, non-blocking)."
|
||||
;; Helper: Find dependency end time
|
||||
;; ------------------------------------------------------------
|
||||
(defun gortium/internal--get-blocker-end (blocker-str task-end-map)
|
||||
"Return latest end time only if blockers are DONE or have been recalculated."
|
||||
"Return the latest end time of all blockers if they are all resolved."
|
||||
(let ((clean (s-trim (format "%s" blocker-str)))
|
||||
(latest-time nil)
|
||||
(all-resolved t))
|
||||
@@ -2329,23 +2352,29 @@ Returns nil if no range found (safe, non-blocking)."
|
||||
(not (string-empty-p (s-trim (match-string 1 clean)))))
|
||||
(dolist (tid (split-string (match-string 1 clean) "[ ,]+" t))
|
||||
(let* ((clean-id (replace-regexp-in-string "[\"']\\|id:" "" tid))
|
||||
(pos (org-id-find clean-id t))
|
||||
(pos (org-id-find clean-id t)) ;; Returns marker OR (file . pos)
|
||||
(computed-end (gethash clean-id task-end-map))
|
||||
(blocker-end
|
||||
(cond
|
||||
;; 1) Use the new time we just calculated in this run (Priority!)
|
||||
(computed-end computed-end)
|
||||
|
||||
;; 2) If it's DONE, use the CLOSED timestamp
|
||||
((and pos (org-entry-get pos "CLOSED"))
|
||||
(org-time-string-to-time (org-entry-get pos "CLOSED")))
|
||||
|
||||
;; 3) If it's FIXED, use the existing range/scheduled time
|
||||
((and pos (string-equal "t" (org-entry-get pos "FIXED")))
|
||||
(or (gortium/org--get-range-start pos) ;; Note: This needs range end logic, but start is a fallback
|
||||
(org-get-scheduled-time pos)))
|
||||
|
||||
;; Otherwise: We MUST wait for this blocker to be recalculated
|
||||
;; Use a temporary buffer context to check properties if pos is a list
|
||||
((and pos (let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos))))))
|
||||
(with-current-buffer (marker-buffer m)
|
||||
(org-with-point-at m
|
||||
(cond
|
||||
((org-entry-get nil "CLOSED")
|
||||
(org-time-string-to-time (org-entry-get nil "CLOSED")))
|
||||
((string-equal "t" (org-entry-get nil "FIXED"))
|
||||
(gortium/org--get-range-end m))
|
||||
(t nil))))))
|
||||
;; This captures the result of the let/with-current-buffer block
|
||||
(let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos))))))
|
||||
(with-current-buffer (marker-buffer m)
|
||||
(org-with-point-at m
|
||||
(if (org-entry-get nil "CLOSED")
|
||||
(org-time-string-to-time (org-entry-get nil "CLOSED"))
|
||||
(gortium/org--get-range-end m))))))
|
||||
(t nil))))
|
||||
|
||||
(if blocker-end
|
||||
@@ -2485,17 +2514,16 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
|
||||
;; --- MAIN SCHEDULER ---
|
||||
(defun gortium/org-schedule-subtree-chains ()
|
||||
"Optimized scheduler: Ignores stale buffer ranges to ensure correct dependency flow."
|
||||
"Standard Gortium scheduler: Correctly calculates Finish-to-Start dependencies."
|
||||
(interactive)
|
||||
(message "=== Starting Gortium Scheduler ===")
|
||||
|
||||
;; 1. Global deactivations to prevent the "Parser Error"
|
||||
(let ((org-element-use-cache nil)
|
||||
(all-tasks '())
|
||||
(let ((all-tasks '())
|
||||
(task-end-times (make-hash-table :test 'equal))
|
||||
(start-time (current-time)))
|
||||
(start-time (current-time))
|
||||
(org-element-use-cache nil)) ;; Disable buggy cache
|
||||
|
||||
;; 2. COLLECTION
|
||||
;; 1. COLLECT
|
||||
(org-map-entries
|
||||
(lambda ()
|
||||
(when (org-get-todo-state)
|
||||
@@ -2512,7 +2540,7 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
|
||||
(setq all-tasks (nreverse all-tasks))
|
||||
|
||||
;; 3. THE LOOP
|
||||
;; 2. ITERATE
|
||||
(let* ((remaining all-tasks)
|
||||
(limit (* 20 (length remaining)))
|
||||
(iter 0))
|
||||
@@ -2524,7 +2552,6 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
(pcase-let ((`(,buf ,pos ,id ,effort ,blocker ,fixed ,sched ,offset) task))
|
||||
(let* ((blocker-end (gortium/internal--get-blocker-end blocker task-end-times))
|
||||
(has-blocker (and blocker (not (string-empty-p (s-trim blocker)))))
|
||||
;; A task is ready if it's FIXED or all blockers are in the task-end-times map
|
||||
(is-fixed (string-equal fixed "t"))
|
||||
(ready (or is-fixed (not has-blocker) blocker-end)))
|
||||
|
||||
@@ -2532,10 +2559,15 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
(with-current-buffer buf
|
||||
(org-element-with-disabled-cache
|
||||
(let* ((off-days (if (stringp offset) (string-to-number offset) 0))
|
||||
;; CRITICAL: For fixed tasks, base-start is THEIR start.
|
||||
;; For dependent tasks, base-start is the blocker's END.
|
||||
(base-start (cond (is-fixed (or (gortium/org--get-range-start pos) sched (current-time)))
|
||||
(t (or blocker-end sched (current-time)))))
|
||||
(t (or blocker-end (or sched (current-time))))))
|
||||
|
||||
(final-start (if is-fixed base-start
|
||||
(gortium/internal--snap-to-working-hours (time-add base-start (days-to-time off-days)))))
|
||||
(gortium/internal--snap-to-working-hours
|
||||
(time-add base-start (days-to-time off-days)))))
|
||||
|
||||
(span (gortium/internal--calculate-task-span final-start effort))
|
||||
(final-end (car span))
|
||||
(wknd (cadr span)))
|
||||
@@ -2544,10 +2576,11 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
(push task done-this-loop))))))))
|
||||
(setq remaining (cl-set-difference remaining done-this-loop))))
|
||||
|
||||
;; 3. CLEANUP
|
||||
(setq org-element-use-cache t)
|
||||
(org-element-cache-reset 'all)
|
||||
(message "=== Scheduler completed (%d tasks, %d iterations) ===" (length all-tasks) iter))))
|
||||
|
||||
|
||||
;; ---------------------------------------------
|
||||
(defun gortium/org-ensure-task-properties ()
|
||||
"Iterate through all tasks (TODO, NEXT, STRT, WAIT, HOLD, DONE, etc.)
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
(org-agenda-start-day "2026-01-29")
|
||||
(org-agenda-overriding-header "📅 Installation Bombardier")
|
||||
(org-agenda-prefix-format " %12t") ;; reserve time space
|
||||
(org-agenda-todo-keyword-format " %-12s ") ;; fixed-width TODO
|
||||
(org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO
|
||||
(org-agenda-tags-column -100) ;; right-align tags
|
||||
(org-agenda-time-grid nil)
|
||||
)))
|
||||
@@ -1594,6 +1594,29 @@ If FILE is nil, refile in the current file."
|
||||
(nthcdr 3 (decode-time next)))))))
|
||||
(t t1))))
|
||||
|
||||
(defun gortium/org--get-range-end (pos)
|
||||
"Extract the END timestamp from an existing range like <A>--<B> at POS.
|
||||
POS can be a marker or a cons cell (file . position)."
|
||||
(let ((marker (if (markerp pos) pos
|
||||
(let ((file (car pos))
|
||||
(p (cdr pos)))
|
||||
(with-current-buffer (find-file-noselect file)
|
||||
(copy-marker p))))))
|
||||
(with-current-buffer (marker-buffer marker)
|
||||
(save-excursion
|
||||
(goto-char marker)
|
||||
(org-back-to-heading t)
|
||||
(let ((subtree-end (save-excursion (org-end-of-subtree t) (point)))
|
||||
(end-time nil))
|
||||
(save-restriction
|
||||
(narrow-to-region (point) subtree-end)
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "<[^>]+>--<\\([^>]+\\)>" nil t)
|
||||
(condition-case nil
|
||||
(setq end-time (org-time-string-to-time (match-string 1)))
|
||||
(error nil))))
|
||||
end-time)))))
|
||||
|
||||
(defun gortium/org--get-range-start (pos)
|
||||
"Extract the start timestamp from an existing range like <A>--<B>.
|
||||
Returns nil if no range found (safe, non-blocking)."
|
||||
@@ -1667,7 +1690,7 @@ Returns nil if no range found (safe, non-blocking)."
|
||||
;; Helper: Find dependency end time
|
||||
;; ------------------------------------------------------------
|
||||
(defun gortium/internal--get-blocker-end (blocker-str task-end-map)
|
||||
"Return latest end time only if blockers are DONE or have been recalculated."
|
||||
"Return the latest end time of all blockers if they are all resolved."
|
||||
(let ((clean (s-trim (format "%s" blocker-str)))
|
||||
(latest-time nil)
|
||||
(all-resolved t))
|
||||
@@ -1675,23 +1698,29 @@ Returns nil if no range found (safe, non-blocking)."
|
||||
(not (string-empty-p (s-trim (match-string 1 clean)))))
|
||||
(dolist (tid (split-string (match-string 1 clean) "[ ,]+" t))
|
||||
(let* ((clean-id (replace-regexp-in-string "[\"']\\|id:" "" tid))
|
||||
(pos (org-id-find clean-id t))
|
||||
(pos (org-id-find clean-id t)) ;; Returns marker OR (file . pos)
|
||||
(computed-end (gethash clean-id task-end-map))
|
||||
(blocker-end
|
||||
(cond
|
||||
;; 1) Use the new time we just calculated in this run (Priority!)
|
||||
(computed-end computed-end)
|
||||
|
||||
;; 2) If it's DONE, use the CLOSED timestamp
|
||||
((and pos (org-entry-get pos "CLOSED"))
|
||||
(org-time-string-to-time (org-entry-get pos "CLOSED")))
|
||||
|
||||
;; 3) If it's FIXED, use the existing range/scheduled time
|
||||
((and pos (string-equal "t" (org-entry-get pos "FIXED")))
|
||||
(or (gortium/org--get-range-start pos) ;; Note: This needs range end logic, but start is a fallback
|
||||
(org-get-scheduled-time pos)))
|
||||
|
||||
;; Otherwise: We MUST wait for this blocker to be recalculated
|
||||
;; Use a temporary buffer context to check properties if pos is a list
|
||||
((and pos (let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos))))))
|
||||
(with-current-buffer (marker-buffer m)
|
||||
(org-with-point-at m
|
||||
(cond
|
||||
((org-entry-get nil "CLOSED")
|
||||
(org-time-string-to-time (org-entry-get nil "CLOSED")))
|
||||
((string-equal "t" (org-entry-get nil "FIXED"))
|
||||
(gortium/org--get-range-end m))
|
||||
(t nil))))))
|
||||
;; This captures the result of the let/with-current-buffer block
|
||||
(let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos))))))
|
||||
(with-current-buffer (marker-buffer m)
|
||||
(org-with-point-at m
|
||||
(if (org-entry-get nil "CLOSED")
|
||||
(org-time-string-to-time (org-entry-get nil "CLOSED"))
|
||||
(gortium/org--get-range-end m))))))
|
||||
(t nil))))
|
||||
|
||||
(if blocker-end
|
||||
@@ -1831,17 +1860,16 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
|
||||
;; --- MAIN SCHEDULER ---
|
||||
(defun gortium/org-schedule-subtree-chains ()
|
||||
"Optimized scheduler: Ignores stale buffer ranges to ensure correct dependency flow."
|
||||
"Standard Gortium scheduler: Correctly calculates Finish-to-Start dependencies."
|
||||
(interactive)
|
||||
(message "=== Starting Gortium Scheduler ===")
|
||||
|
||||
;; 1. Global deactivations to prevent the "Parser Error"
|
||||
(let ((org-element-use-cache nil)
|
||||
(all-tasks '())
|
||||
(let ((all-tasks '())
|
||||
(task-end-times (make-hash-table :test 'equal))
|
||||
(start-time (current-time)))
|
||||
(start-time (current-time))
|
||||
(org-element-use-cache nil)) ;; Disable buggy cache
|
||||
|
||||
;; 2. COLLECTION
|
||||
;; 1. COLLECT
|
||||
(org-map-entries
|
||||
(lambda ()
|
||||
(when (org-get-todo-state)
|
||||
@@ -1858,7 +1886,7 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
|
||||
(setq all-tasks (nreverse all-tasks))
|
||||
|
||||
;; 3. THE LOOP
|
||||
;; 2. ITERATE
|
||||
(let* ((remaining all-tasks)
|
||||
(limit (* 20 (length remaining)))
|
||||
(iter 0))
|
||||
@@ -1870,7 +1898,6 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
(pcase-let ((`(,buf ,pos ,id ,effort ,blocker ,fixed ,sched ,offset) task))
|
||||
(let* ((blocker-end (gortium/internal--get-blocker-end blocker task-end-times))
|
||||
(has-blocker (and blocker (not (string-empty-p (s-trim blocker)))))
|
||||
;; A task is ready if it's FIXED or all blockers are in the task-end-times map
|
||||
(is-fixed (string-equal fixed "t"))
|
||||
(ready (or is-fixed (not has-blocker) blocker-end)))
|
||||
|
||||
@@ -1878,10 +1905,15 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
(with-current-buffer buf
|
||||
(org-element-with-disabled-cache
|
||||
(let* ((off-days (if (stringp offset) (string-to-number offset) 0))
|
||||
;; CRITICAL: For fixed tasks, base-start is THEIR start.
|
||||
;; For dependent tasks, base-start is the blocker's END.
|
||||
(base-start (cond (is-fixed (or (gortium/org--get-range-start pos) sched (current-time)))
|
||||
(t (or blocker-end sched (current-time)))))
|
||||
(t (or blocker-end (or sched (current-time))))))
|
||||
|
||||
(final-start (if is-fixed base-start
|
||||
(gortium/internal--snap-to-working-hours (time-add base-start (days-to-time off-days)))))
|
||||
(gortium/internal--snap-to-working-hours
|
||||
(time-add base-start (days-to-time off-days)))))
|
||||
|
||||
(span (gortium/internal--calculate-task-span final-start effort))
|
||||
(final-end (car span))
|
||||
(wknd (cadr span)))
|
||||
@@ -1890,10 +1922,11 @@ Returns list of task IDs involved in cycles, or nil if no cycles found."
|
||||
(push task done-this-loop))))))))
|
||||
(setq remaining (cl-set-difference remaining done-this-loop))))
|
||||
|
||||
;; 3. CLEANUP
|
||||
(setq org-element-use-cache t)
|
||||
(org-element-cache-reset 'all)
|
||||
(message "=== Scheduler completed (%d tasks, %d iterations) ===" (length all-tasks) iter))))
|
||||
|
||||
|
||||
;; ---------------------------------------------
|
||||
(defun gortium/org-ensure-task-properties ()
|
||||
"Iterate through all tasks (TODO, NEXT, STRT, WAIT, HOLD, DONE, etc.)
|
||||
|
||||
Reference in New Issue
Block a user