danielmiessler / danielmiessler/LifeOS
WorkSweep: project ISAs never reach the board, completed sessions land in Queued, goals regex cuts at "Z"
- Dominant language
- TypeScript
- Stars
- 19k
- Forks
- 2.5k
- Avg merge
- 8d 17h
- Merged PRs (30d)
- 1
Description
Upstream issue draft. Target: `LIFEOS/TOOLS/WorkSweep.ts` as shipped in LifeOS 7.40.4. Three defects in one file, one patch. Diff attached below under Patch.
## 1. The session catch-up walks one ISA home; canon has two
`ISASystem.md` sanctions two ISA homes: `MEMORY/WORK/{slug}/ISA.md` for tasks and `/ISA.md` for persistent things ("The ISA lives in the project's repo as system of record"). `hooks/ISASync.hook.ts` was fixed for the second home in public issue #1807 and now records a project ISA in `work.json` under the slug `project-`.
`WorkSweep.ts` still enumerates `MEMORY/WORK/` only (`listSessionDirs()` is a `readdirSync(WORK_DIR)`). `WorkSystem.md` says the private SessionEnd hook is the first capture surface and "a fresh public install ships without it; sweep catches everything else within an hour". On a public install the sweep is therefore the only capture surface, and it cannot see a project ISA. Result: a project ISA is tracked in `work.json` and never gets an issue.
`work.json` cannot be the fix: `syncToWorkJson` writes `task, sessionUUID, phase, progress, started, updatedAt, ratings, sessionName, lastToolActivity, ascent`. There is no path field, so the registry cannot lead the sweep from `project-` back to a file.
**Fix.** Enumerate the second home directly: every `PROJECTS.md` row (the `| **Name** | \`path\` |` contract `parseProjectsMd()` already parses for the project check) whose path holds an `ISA.md`. Derive the slug the same way ISASync does when the frontmatter has none, so both sides agree on the key. 17 changed lines, no new config, no new dependency.
## 2. A session already at phase complete is created open in Queued
The sweep creates every caught session with `Status:queued`. A session whose ISA is already at phase `complete` is a record of finished work, not a queue item. With no SessionEnd hook on a public install, nothing ever closes it, so Queued fills with finished folders.
**Fix.** For task ISAs only (directory under `MEMORY/WORK/`): when `phaseBracket(fm.phase) === "cairn"`, create with `Status:complete` and close the issue at once with a comment naming the ISA (`ghCloseIssue`). Dry run prints `[closed]` on the line. A project ISA is never complete by doctrine (`ISASystem.md`: project ISAs "are never completed"), and `ghIssueSearchSlug` runs `gh issue list --state all`, so a closed project card would block the sweep from ever recreating it. Project ISAs always land open with `Status:queued`.
## 3. Active Goals regex uses `\Z`
```ts
content.match(/## Active Goals[\s\S]*?(?=\n## |\Z)/)
```
`\Z` is not an end-of-string anchor in JavaScript; it is a literal `Z`. The section was cut at the first capital Z in the goals text: any goal containing a capital Z lost everything from that Z onward. `$` without the `m` flag is end of string.
```ts
content.match(/## Active Goals[\s\S]*?(?=\n## |$)/)
```
## Test
Point `LIFEOS_DIR` at a fixture tree holding a `PROJECTS.md` row for a repo that contains an `ISA.md`, an empty `MEMORY/WORK/`, and the install's `USER/WORK/work_repo.json`. Run:
```
LIFEOS_DIR= bun LIFEOS/TOOLS/WorkSweep.ts --dry-run --since 2000h
```
Expected: one `+ would create [Sweep] (project-) labels=...,Status:queued` line for the project ISA, no `[closed]`, nothing created (dry run), `worksweep.jsonl` appended inside the fixture only. Before the patch the same command prints no line for it.
## Patch
```diff
--- LIFEOS/TOOLS/WorkSweep.ts 2026-09-04 10:48:59
+++ LIFEOS/TOOLS/WorkSweep.ts (patched) 2026-09-04 16:09:54
@@ -34,7 +34,7 @@
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, appendFileSync } from "fs";
import { getDAName } from "../../hooks/lib/identity"
-import { join } from "path";
+import { join, basename } from "path";
import { loadWorkConfig } from "../../hooks/lib/work-config";
import { phaseHasWorkStarted, phaseBracket } from "./ascent";
import { homedir } from "node:os";
@@ -109,6 +109,17 @@
.filter((p) => statSync(p).isDirectory() && existsSync(join(p, "ISA.md")));
}
+// Second ISA home. The Algorithm sanctions two: MEMORY/WORK/{slug}/ISA.md for tasks and
+// /ISA.md for persistent things. ISASync records both in work.json (public issue
+// #1807), but this sweep walked MEMORY/WORK only, so a project ISA never reached the board.
+// work.json carries no path, so the home itself is enumerated: every PROJECTS.md row whose
+// path holds an ISA.md.
+function listProjectIsaDirs(): string[] {
+ return parseProjectsMd()
+ .map((r) => r.pathLocal)
+ .filter((p) => existsSync(join(p, "ISA.md")));
+}
+
function isMeaningfulWork(fm: ISAFm, isaPath: string): boolean {
// Skip empty/abandoned scaffolds — no progress, no phase advance.
// ALGORITHM sessions: the run must have left articulation. Resolved through
@@ -249,7 +260,7 @@
stats: SweepStats,
): Promise {
const cutoff = Date.now() - sinceMs;
- const dirs = listSessionDirs();
+ const dirs = [...listSessionDirs(), ...listProjectIsaDirs()];
for (const dir of dirs) {
const isaPath = join(dir, "ISA.md");
const mtime = statSync(isaPath).mtimeMs;
@@ -258,6 +269,9 @@
const content = readFileSync(isaPath, "utf-8");
const fm = parseFrontmatter(content);
+ // A project ISA usually has no slug: derive it the way ISASync does, so the sweep and
+ // the registry agree on the key (`project-`, public issue #1807).
+ if (fm && !fm.slug && !dir.startsWith(WORK_DIR)) fm.slug = "project-" + basename(dir).toLowerCase();
if (!fm || !fm.slug) continue;
if (fm.github_issue) continue; // already synced
@@ -271,7 +285,10 @@
// A session already at the cairn (phase complete) is a record of done work, not a
// queue item. It lands closed with Status:complete so Queued only holds live work
// (a finished folder merge sat in Queued with nothing to close it).
- const isComplete = phaseBracket(fm.phase) === "cairn";
+ // Task ISAs only: a project ISA is never complete by doctrine (ISASystem.md, Two Homes:
+ // project ISAs "are never \"completed\""), and ghIssueSearchSlug matches closed issues,
+ // so a closed project card could never come back. Project ISAs land open, Status:queued.
+ const isComplete = dir.startsWith(WORK_DIR) && phaseBracket(fm.phase) === "cairn";
const titlePrefix = isNative ? "[Native]" : "[Sweep]";
const title = `${titlePrefix} ${taskOrSlug(fm)} [slug:${fm.slug}]`;
const labels = filterLabels([
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in LIFEOS/TOOLS/WorkSweep.ts, reading listSessionDirs(), parseProjectsMd(), the sweep loop, and the Active Goals regex. Run the provided LIFEOS_DIR fixture command with --dry-run; done means the project ISA appears as queued, completed task ISAs are reported closed, goals containing capital Z are preserved, and nothing is created in dry-run mode.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun, typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100