@litfamily/litmuse 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -1
- package/README_ko-KR.md +3 -1
- package/TRADEMARK.md +12 -0
- package/lib/stage-card.mjs +97 -29
- package/package.json +3 -2
- package/plugin/.muse-plugin/plugin.json +11 -11
- package/plugin/commands/lit-status.md +2 -1
- package/plugin/hooks/lib/next-step.mjs +48 -4
- package/plugin/hooks/lib/stage.mjs +53 -6
- package/plugin/hooks/lib/workflow-stack.mjs +21 -3
- package/plugin/hooks/post-tool-use-failure.mjs +2 -0
- package/plugin/hooks/post-tool-use.mjs +2 -0
- package/plugin/hooks/session-end.mjs +2 -1
- package/plugin/hooks/session-start.mjs +4 -1
- package/plugin/hooks/stop.mjs +23 -3
- package/plugin/hooks/user-prompt-submit.mjs +1 -1
- package/plugin/lib/rewind/collect.mjs +191 -62
- package/plugin/lib/rewind/transcripts.mjs +2 -0
- package/plugin/skills/lit-adhd/SKILL.md +19 -12
- package/plugin/skills/lit-adhd/references/status-taxonomy.md +6 -4
- package/plugin/skills/lit-plan/SKILL.md +12 -1
- package/plugin/skills/lit-plan/scripts/check-plan.mjs +71 -4
- package/plugin/skills/lit-team/scripts/state-root.mjs +5 -18
- package/plugin/skills/start-work/SKILL.md +4 -1
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# lit-adhd status taxonomy
|
|
2
2
|
|
|
3
3
|
The collector assigns every session exactly one status. The brief renders it in the user's language
|
|
4
|
-
with the words below and never invents
|
|
4
|
+
with the words below and never invents another.
|
|
5
5
|
|
|
6
6
|
| Status | Set when | English | 한국어 |
|
|
7
7
|
| --- | --- | --- | --- |
|
|
8
8
|
| `waiting-on-user` | The last transcript entry is the assistant asking something (a closing `?`, `lit approve`, `확인해 주세요`, `할까요`…), or the session holds a paused workflow frame or a start-work boundary | waiting on you | 나를 기다림 |
|
|
9
9
|
| `active` | A transcript write within the last 15 minutes and not waiting | active | 진행 중 |
|
|
10
|
-
| `
|
|
10
|
+
| `idle` | Another session, otherwise active, whose last hook event is a Stop no older than its last log line | idle | 쉬는 중 |
|
|
11
|
+
| `done` | Another session, otherwise active, whose start-work plan was fully ticked after its last log line | done | 완료 |
|
|
12
|
+
| `ended` | Quiet for longer than 15 minutes, or its last hook event was SessionEnd | ended | 끝남 |
|
|
11
13
|
| `unknown` | The transcript held no timestamp | unknown | 모름 |
|
|
12
14
|
|
|
13
15
|
`waitingReason` says why a session waits: `asked the user`, `paused workflow` or
|
|
14
16
|
`start-work boundary`. Name it in the card's status cell when present: `waiting on you (paused
|
|
15
17
|
workflow)` / `나를 기다림 (멈춘 워크플로)`.
|
|
16
18
|
|
|
17
|
-
Order in the brief: waiting sessions first, then active ones as full cards, newest first inside each group; ended sessions collapse into one short row each after the cards.
|
|
19
|
+
Order in the brief: waiting sessions first, then active, idle and done ones as full cards, newest first inside each group; ended sessions collapse into one short row each after the cards. A done card's next line names the plan its run finished (`start-work finished plans/a.md` / `plans/a.md start-work 완료`).
|
|
18
20
|
|
|
19
21
|
## Sources
|
|
20
22
|
|
|
@@ -29,7 +31,7 @@ line and never filled in from memory.
|
|
|
29
31
|
| `handoffs` | `HANDOFF*.md` and `.litmuse/handoff/latest.md` headings |
|
|
30
32
|
| `git` | `git log --since` |
|
|
31
33
|
| `workflowStack` | `.litmuse/workflow-stack/*.json` |
|
|
32
|
-
| `startWork` | the session's
|
|
34
|
+
| `startWork` | the stage records: this session's live run, any session's completed run |
|
|
33
35
|
| `herdr` | `herdr agent list`, only with `HERDR_ENV=1` |
|
|
34
36
|
|
|
35
37
|
## Next-step kinds
|
|
@@ -169,6 +169,17 @@ Detail earns its place when it removes executor judgment, protects a boundary or
|
|
|
169
169
|
8. The final verification wave.
|
|
170
170
|
9. Commit strategy, success criteria, DoneClaim, risks and open questions.
|
|
171
171
|
|
|
172
|
+
### Scope roots for outside folders
|
|
173
|
+
|
|
174
|
+
When a todo names an absolute path outside the project (a shared notes folder, a sibling checkout, a temp area), the plan grants it before the work starts, with one line per folder anywhere outside a code fence:
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
Scope roots: /abs/path/to/folder (read)
|
|
178
|
+
Scope roots: /abs/path/to/scratch (read, write, execute)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Grant the narrowest existing folder and only the actions the todos need. Paths inside the project need no line. A command quoted in a fence or an inline code span (it holds a space) is data and needs no line; a code span that is one bare path counts. Before saving, search the Todos for `/` paths and add the missing lines. `start-work` runs `node scripts/check-plan.mjs --check plans/<slug>.md`, which fails on an uncovered folder and prints the exact line to add; a filesystem-root, top-level or home folder is never suggested, so name a narrower one.
|
|
182
|
+
|
|
172
183
|
A task that builds or changes a user-facing web interface names `frontend-ui-ux` in its verification (the interface probe across the responsive matrix, with no HIGH finding left) and hands the acceptance verdict to `visual-qa`. A CLI, library or backend-only task names neither.
|
|
173
184
|
|
|
174
185
|
### Plan-row grammar (machine-checkable)
|
|
@@ -337,7 +348,7 @@ Refused on this route: `create_goal`, `update_goal`, `write_todos`, `add_memory`
|
|
|
337
348
|
|
|
338
349
|
Stop when the plan file is written and passes the checklist, or when a missing answer blocks every useful task (then the plan carries the question as row 1 and the reply says `BLOCKED: <question>`). After two failed attempts at one section, surface what you tried and ask.
|
|
339
350
|
|
|
340
|
-
Reply in reader form: the plan path, the TL;DR, the number of tasks, any assumption the user should confirm, and the next action:
|
|
351
|
+
Reply in reader form: the plan path, the TL;DR, the number of tasks, any assumption the user should confirm, and the next action. End the reply with this line, plan name filled in: "Type `go` within 30 minutes to start `plans/<slug>.md` with start-work, or run `/start-work` later." The Stop hook records the saved plan for this session only, so `go` from another session, or after 30 minutes, starts nothing. Approval covers plan generation; implementation, commit, push, versioning and publishing each need their own go-ahead. Keep logs, lane verdicts and command lists in the draft and ledger unless the user asks for them.
|
|
341
352
|
|
|
342
353
|
## Pitfalls
|
|
343
354
|
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
// Exit 0 = pass, 1 = findings, 2 = usage. A planning turn cannot run this (the route policy refuses node);
|
|
11
11
|
// start-work, a terminal and the test suite do.
|
|
12
12
|
|
|
13
|
-
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
14
|
-
import {
|
|
13
|
+
import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
15
16
|
import { fileURLToPath } from 'node:url';
|
|
16
17
|
|
|
17
18
|
export const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
@@ -56,7 +57,67 @@ export function visibleLines(text) {
|
|
|
56
57
|
return out;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
// `Scope roots: <path> (<action>, <action>)`: an outside folder the plan's todos reach, granted up front.
|
|
61
|
+
export const SCOPE_ROOT_LINE = /^Scope roots?:[ \t]*(\S+)[ \t]*\(([a-z, -]+)\)[ \t]*$/u;
|
|
62
|
+
// An absolute path with at least two segments. Inside an inline code span only a span that is one bare
|
|
63
|
+
// path counts; a span holding spaces is a quoted command, which is data rather than a folder.
|
|
64
|
+
const ABSOLUTE_PATH = /(?<![\w.~/-])\/[\w.@+%,=~-]+(?:\/[\w.@+%,=~-]+)+\/?/gu;
|
|
65
|
+
const pathsIn = (line) => line
|
|
66
|
+
.replace(/`([^`]*)`/gu, (_, span) => (/\s/u.test(span.trim()) ? ' ' : ` ${span.trim()} `))
|
|
67
|
+
.match(ABSOLUTE_PATH) ?? [];
|
|
68
|
+
|
|
69
|
+
const realOr = (path) => {
|
|
70
|
+
try {
|
|
71
|
+
return realpathSync(path);
|
|
72
|
+
} catch {
|
|
73
|
+
return path;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const within = (root, path) => {
|
|
77
|
+
const rel = relative(root, path);
|
|
78
|
+
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
79
|
+
};
|
|
80
|
+
const existingFolder = (path) => {
|
|
81
|
+
for (let at = path; ; at = dirname(at)) {
|
|
82
|
+
try {
|
|
83
|
+
if (statSync(at).isDirectory()) return at;
|
|
84
|
+
} catch { /* not there: climb */ }
|
|
85
|
+
if (dirname(at) === at) return at;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Absolute paths named under `## Todos` (up to `## Final verification wave`, fences skipped) that lie
|
|
91
|
+
* outside `projectRoot` and outside every `Scope roots:` line, one entry per folder: `{path, suggestion}`
|
|
92
|
+
* with the line to add, or `{path, problem}` when the nearest existing folder is too broad to grant.
|
|
93
|
+
*/
|
|
94
|
+
export function uncoveredScopePaths(text, projectRoot) {
|
|
95
|
+
const project = realOr(resolve(projectRoot));
|
|
96
|
+
const lines = visibleLines(text);
|
|
97
|
+
const granted = lines.map(({ line }) => SCOPE_ROOT_LINE.exec(line.trim())).filter(Boolean)
|
|
98
|
+
.map(([, path]) => realOr(isAbsolute(path) ? path : resolve(project, path)));
|
|
99
|
+
const found = new Map();
|
|
100
|
+
let todos = false;
|
|
101
|
+
for (const { line } of lines) {
|
|
102
|
+
const heading = line.trimEnd();
|
|
103
|
+
if (heading === '## Todos') todos = true;
|
|
104
|
+
else if (/^## /u.test(heading)) todos = false;
|
|
105
|
+
if (!todos) continue;
|
|
106
|
+
for (const raw of pathsIn(line)) {
|
|
107
|
+
const path = raw.replace(/[.,:;)]+$/u, '');
|
|
108
|
+
const folder = realOr(existingFolder(path));
|
|
109
|
+
const covered = [realOr(path), folder].some((p) => within(project, p) || granted.some((root) => within(root, p)));
|
|
110
|
+
if (covered || found.has(folder)) continue;
|
|
111
|
+
const tooBroad = dirname(folder) === dirname(dirname(folder)) || folder === realOr(homedir());
|
|
112
|
+
found.set(folder, tooBroad
|
|
113
|
+
? { path, problem: `${path} is outside the project; name a narrower existing folder on a Scope roots line` }
|
|
114
|
+
: { path, suggestion: `Scope roots: ${folder} (read)` });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return [...found.values()];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function checkPlan(text, { complete = false, projectRoot = null } = {}) {
|
|
60
121
|
const problems = [];
|
|
61
122
|
const lines = visibleLines(text);
|
|
62
123
|
|
|
@@ -115,6 +176,10 @@ export function checkPlan(text, { complete = false } = {}) {
|
|
|
115
176
|
|
|
116
177
|
if (/<fill\b/u.test(text)) problems.push('unfilled <fill ...> placeholders remain: the plan is not decision-complete');
|
|
117
178
|
|
|
179
|
+
if (projectRoot) {
|
|
180
|
+
for (const entry of uncoveredScopePaths(text, projectRoot)) problems.push(entry.problem ?? `${entry.path} is outside the project and no Scope roots line covers it; add: ${entry.suggestion}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
118
183
|
if (complete) {
|
|
119
184
|
for (const { line } of marked) {
|
|
120
185
|
if (/^- \[ \] /u.test(line)) problems.push(`row still open: ${line.trim()}`);
|
|
@@ -138,7 +203,9 @@ function checkFile(planPath, options) {
|
|
|
138
203
|
}
|
|
139
204
|
if (stat.isSymbolicLink() || !stat.isFile()) return fail(`FAIL PLAN_UNSAFE: ${path} must be a regular file`);
|
|
140
205
|
if (stat.size > MAX_PLAN_BYTES) return fail(`FAIL PLAN_TOO_LARGE: ${path}`);
|
|
141
|
-
|
|
206
|
+
// The project is the folder holding plans/; a plan kept elsewhere is checked against the cwd.
|
|
207
|
+
const projectRoot = basename(dirname(path)) === 'plans' ? dirname(dirname(path)) : process.cwd();
|
|
208
|
+
const result = checkPlan(readFileSync(path, 'utf8'), { ...options, projectRoot });
|
|
142
209
|
if (!result.ok) {
|
|
143
210
|
for (const problem of result.problems) process.stderr.write(`FAIL ${problem}\n`);
|
|
144
211
|
return 1;
|
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
// state-root.mjs: where LitMuse keeps project-local working state.
|
|
2
2
|
// State belongs at the outermost Git root (or the start directory when there is no repository), so a nested
|
|
3
|
-
// package never grows its own .litmuse folder.
|
|
3
|
+
// package never grows its own .litmuse folder; a linked worktree keeps its own. The rule lives once, in the
|
|
4
|
+
// hooks' workflow-stack module, so team state lands where the hooks and the status card look.
|
|
4
5
|
|
|
5
|
-
import { closeSync, constants,
|
|
6
|
+
import { closeSync, constants, lstatSync, openSync, writeSync } from "node:fs";
|
|
6
7
|
import { basename, dirname, join, resolve } from "node:path";
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
const outermostGitRoot = (startDir) => {
|
|
11
|
-
let root = null;
|
|
12
|
-
let current = startDir;
|
|
13
|
-
for (;;) {
|
|
14
|
-
if (existsSync(join(current, ".git"))) root = current;
|
|
15
|
-
const parent = dirname(current);
|
|
16
|
-
if (parent === current) return root;
|
|
17
|
-
current = parent;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
9
|
+
export { resolveProjectStateRoot } from "../../../hooks/lib/workflow-stack.mjs";
|
|
20
10
|
|
|
21
|
-
export const
|
|
22
|
-
const start = resolve(startDir);
|
|
23
|
-
return outermostGitRoot(start) ?? start;
|
|
24
|
-
};
|
|
11
|
+
export const PROJECT_STATE_DIR = ".litmuse";
|
|
25
12
|
|
|
26
13
|
const STATE_GITIGNORE = "# Created by LitMuse: local working state, not for commits.\n*\n";
|
|
27
14
|
|
|
@@ -74,7 +74,10 @@ okay. When the two disagree, ask.
|
|
|
74
74
|
|
|
75
75
|
### Authority envelope
|
|
76
76
|
|
|
77
|
-
The envelope is the plan's Scope
|
|
77
|
+
The envelope is the plan's Scope, its `Scope roots: <path> (<actions>)` lines and the user's messages. A
|
|
78
|
+
`Scope roots` line grants exactly its listed actions under that folder outside the workspace; a todo that reaches
|
|
79
|
+
an uncovered outside folder fails the structure check (`check-plan.mjs --check` prints the line to add), so ask
|
|
80
|
+
the user before treating that folder as in scope. Work inside the envelope freely:
|
|
78
81
|
|
|
79
82
|
- read: any file in the workspace;
|
|
80
83
|
- write: the paths on the current row's Files line, new test files for that row, `.litmuse/` records, and the one
|