@lumoai/cli 1.27.0 → 1.28.0

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.
@@ -19,20 +19,20 @@ which lumo && lumo whoami
19
19
 
20
20
  The command catalog below is a **map**: it lists every command grouped by domain with a one-line summary. **Detailed flags, examples, output formats, and "when to suggest" guidance live in the `references/` files** — when a user request lands in a domain, **Read the matching reference file before composing the command**. Don't run a command from memory if its flags/edge-cases matter; open the reference first.
21
21
 
22
- | Domain | Read this reference |
23
- | --------------------------------------------------------------------------------- | -------------------------------------------------------------- |
24
- | `setup`, `auth login/logout`, `whoami`, `update` | [references/onboarding.md](references/onboarding.md) |
25
- | `task context`, retrieval (`slack/web/figma context`, `comments list`, `pr show`) | [references/task-context.md](references/task-context.md) |
26
- | `task create/update/list/show/comment`, `next` | [references/tasks.md](references/tasks.md) |
27
- | `task artifact*`, `task figma*` | [references/artifacts-figma.md](references/artifacts-figma.md) |
28
- | `task criteria set/list`, drafting the acceptance contract | [references/criteria.md](references/criteria.md) |
22
+ | Domain | Read this reference |
23
+ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
24
+ | `setup`, `auth login/logout`, `whoami`, `update` | [references/onboarding.md](references/onboarding.md) |
25
+ | `task context`, retrieval (`slack/web/figma context`, `comments list`, `pr show`) | [references/task-context.md](references/task-context.md) |
26
+ | `task create/update/list/show/comment`, `next` | [references/tasks.md](references/tasks.md) |
27
+ | `task artifact*`, `task figma*` | [references/artifacts-figma.md](references/artifacts-figma.md) |
28
+ | `task criteria set/list`, drafting the acceptance contract | [references/criteria.md](references/criteria.md) |
29
29
  | `verify`, `task status` — machine verification loop, claim-done flow, self-check/resume | [references/verify.md](references/verify.md) |
30
- | `project list`, `milestone*` | [references/milestones.md](references/milestones.md) |
31
- | `doc*` | [references/docs.md](references/docs.md) |
32
- | `sprint*` | [references/sprints.md](references/sprints.md) |
33
- | `task/project memory`, `memory promote/rm` | [references/memory.md](references/memory.md) |
34
- | `session attach/status/detach/wrap`, git-suggest on start, Layer-2 review | [references/sessions.md](references/sessions.md) |
35
- | `worktree add/rm/list` (local dev tooling) | [references/worktree.md](references/worktree.md) |
30
+ | `project list`, `milestone*` | [references/milestones.md](references/milestones.md) |
31
+ | `doc*` | [references/docs.md](references/docs.md) |
32
+ | `sprint*` | [references/sprints.md](references/sprints.md) |
33
+ | `task/project memory`, `memory promote/rm` | [references/memory.md](references/memory.md) |
34
+ | `session attach/status/detach/wrap`, git-suggest on start, Layer-2 review | [references/sessions.md](references/sessions.md) |
35
+ | `worktree add/rm/list` (local dev tooling) | [references/worktree.md](references/worktree.md) |
36
36
 
37
37
  ## Command catalog
38
38
 
@@ -151,10 +151,10 @@ Typical flow when a user says "help me with LUM-42":
151
151
  1. `lumo session attach LUM-42` — bind this session
152
152
  2. `lumo task context LUM-42` — load background
153
153
  3. Review unresolved items, PR-review todos, and the task description
154
- 4. **If the task has no acceptance criteria** (context shows the draft reminder instead of a contract): draft 3–7 outcome-level criteria and submit them with `lumo task criteria set` **before writing the first line of code** — see [criteria.md](references/criteria.md) for the drafting guide
154
+ 4. **If the task has no acceptance criteria** (context shows the draft reminder instead of a contract): draft outcome-level criteria sized to the task (3–7 for a typical multi-file task; 1–2 for a micro task) and submit them with `lumo task criteria set` **before writing the first line of code** — see [criteria.md](references/criteria.md) for the drafting guide
155
155
  5. Begin working on the task
156
156
  6. **Before claiming the work is done: run `lumo verify`** — the machine half of the acceptance loop. Fix failures and re-run (round cap 3). On all-pass the task moves to IN_REVIEW and you stop; never set DONE yourself after a verify loop — that adjudication is human-only. See [verify.md](references/verify.md)
157
157
 
158
158
  **Status-first recovery:** when you pick a task back up — a new session resuming earlier work, or a task that came back after a rejected verification round / review findings — run `lumo task status` **before** re-reading code or planning. It tells you where the loop stands (current round, what passed, what's unmet and why, any REVIEW_ADDED criteria appended during review) so you don't redo finished work or miss the reason it bounced. See [verify.md](references/verify.md)
159
159
 
160
- **Git-suggest at start:** when the session is unbound, session-start may infer the task from the git branch / recent commits and print a suggestion — `Detected LUM-N (from branch name). Run lumo session attach LUM-N to bind.` (or `from recent commits`) — **without** binding. Confirm it's the right task, then run `lumo session attach <LUM-N>` yourself (binding only happens on an explicit attach). See [sessions.md](references/sessions.md) for the full session-start behavior.
160
+ **Git-suggest at start:** when the session is unbound, session-start may infer the task from the git branch / recent commits (any team prefix, e.g. `SPEC-12`, not just `LUM-N`) and print a suggestion — `Detected LUM-N (from branch name). Run lumo session attach LUM-N to bind.` (or `from recent commits`) — **without** binding. Confirm it's the right task, then run `lumo session attach <LUM-N>` yourself (binding only happens on an explicit attach). See [sessions.md](references/sessions.md) for the full session-start behavior.
@@ -17,8 +17,10 @@ contract, you MUST draft and submit criteria before starting implementation:
17
17
  1. Read the task description, comments, linked resources, and memory first —
18
18
  the contract distills what "done" means, so understand the task before
19
19
  writing it.
20
- 2. Draft **3–7 criteria** (soft range — the server warns outside it but never
21
- rejects; if you genuinely need more, merge related checks instead).
20
+ 2. Draft **3–7 criteria** for a typical multi-file task (soft range — the
21
+ server warns outside it but never rejects; if you genuinely need more,
22
+ merge related checks instead). **Scale the count down for small tasks** —
23
+ see "Scale the contract to the task size" below.
22
24
  3. Submit with `lumo task criteria set <task> --file <criteria.json>`.
23
25
  Submission **locks the contract for agent edits** — get it right before
24
26
  submitting, because afterwards only human revisions (`--human`) can change it.
@@ -27,6 +29,23 @@ Once you (the agent) have started work, you can NOT change your own contract.
27
29
  That's by design: the contract is what your work gets judged against, not a
28
30
  to-do list you trim to fit what you built.
29
31
 
32
+ ## Scale the contract to the task size
33
+
34
+ The 3–7 range is calibrated for typical multi-file tasks. The criterion count
35
+ must track the size of the work — it is not a fixed ritual:
36
+
37
+ - **Micro task** (expected to fit a single session, blast radius of one or
38
+ two files — a docs tweak, a copy change, a small targeted fix): **1–2
39
+ criteria IS a complete contract** — one targeted MACHINE criterion plus at
40
+ most one HUMAN criterion.
41
+ - **Never pad toward the soft floor.** A filler criterion (a restated
42
+ baseline, a third angle on the same check) dilutes the contract and taxes
43
+ every verification round. For a micro task the below-minimum warning is
44
+ expected — ignore it.
45
+ - **Shrink the contract, never skip it.** Every task still drafts and locks a
46
+ contract before the first line of code; task size only changes how many
47
+ criteria that takes.
48
+
30
49
  ## How to write good criteria
31
50
 
32
51
  - **Outcome-level definition of done, not micro-steps.** A criterion states a
@@ -129,8 +148,10 @@ before a `--human` revision. Empty contract prints a drafting pointer.
129
148
  binding.
130
149
  - **`lumo task context`**: the `## Acceptance criteria (contract)` section appears after the
131
150
  task description, before memory.
132
- - Review-time gap findings (`REVIEW_ADDED`, appended at the round they
133
- surface) arrive via the verification loop, not via `criteria set`.
151
+ - Review-time gap findings are appended at the round they surface and show up
152
+ in the contract automatically `REVIEW_ADDED` provenance via human review
153
+ paths; findings a human raises in conversation are transcribed with
154
+ `--human` + `--cause` (see verify.md "Review-time drift habits").
134
155
 
135
156
  ## After the contract: the verification loop
136
157
 
@@ -10,15 +10,18 @@ infer the task from local git so it can **suggest** one — it never binds for y
10
10
 
11
11
  - It reads the **current branch name** first (e.g. `lumo/LUM-145-...`), then the
12
12
  **most recent commit subjects** (e.g. `... [LUM-145]`), extracting the first
13
- `LUM-<n>`.
13
+ task identifier. Detection is **prefix-agnostic** (LUM-419): any team prefix
14
+ matches — `SPEC-12` as much as `LUM-145` — using the same pattern the server
15
+ uses to link PR branches to tasks. Well-known acronym-number tokens
16
+ (`UTF-8`, `SHA-256`, `ISO-8601`, …) are skipped, never suggested.
14
17
  - On a hit it prints a single suggestion line and stops — the session stays
15
18
  **unbound** and no context is injected yet:
16
19
  `Detected LUM-145 (from branch name). Run lumo session attach LUM-145 to bind.`
17
20
  (the basis reads `from recent commits` when the hit came from commit subjects instead of the branch name)
18
21
  No task title is shown here because nothing was fetched; the title, memory,
19
22
  and PR-review todos appear only once you actually attach.
20
- - No match (detached HEAD, a non-lumo branch with no tagged commits, not a git
21
- repo) → it degrades to the normal unbound prompt.
23
+ - No match (detached HEAD, a branch with no identifier and no tagged commits,
24
+ not a git repo) → it degrades to the normal unbound prompt.
22
25
 
23
26
  **Agent guidance:** when you see a suggestion line, confirm the inferred task is
24
27
  the one the user wants, then run `lumo session attach <LUM-N>` (followed by
@@ -71,6 +71,30 @@ the failures — re-running without changes burns a round and (at round 3)
71
71
  pages a human. A FAIL round never changes task status; only an all-pass round
72
72
  moves it (to IN_REVIEW, never further).
73
73
 
74
+ ## Review-time drift habits (gap findings)
75
+
76
+ A problem discovered during acceptance/review that the contract does NOT
77
+ cover is a **gap finding** — record it in the contract, never just fix it
78
+ silently:
79
+
80
+ 1. **Append it on the spot.** Transcribe the human's finding as a criterion
81
+ via `lumo task criteria set <task> --file <desired-final-list> --human` —
82
+ the review-added semantics: the gap surfaced at review time, at the
83
+ current round.
84
+ 2. **Tag why the contract drifted** with `--cause
85
+ <NEW_INFO|SCOPE_CHANGE|DRAFT_BLIND_SPOT|GRANULARITY|OTHER>`. Gap findings
86
+ are usually `DRAFT_BLIND_SPOT` (the draft missed it) or `NEW_INFO`
87
+ (information that didn't exist at drafting time).
88
+ 3. **Then bounce.** The appended criterion shows up in `lumo task status`
89
+ nextActions and the next verify round picks it up automatically — no
90
+ side-channel to-do list.
91
+
92
+ How to read drift: information-lag and requirement-movement drift
93
+ (`NEW_INFO`, `SCOPE_CHANGE`) is healthy — don't optimize it away.
94
+ `DRAFT_BLIND_SPOT` clusters feed back into the drafting guide. **Zero drift
95
+ across many tasks is a red flag, not a trophy** — it usually means contracts
96
+ are too thin or state only sure-win clauses that can never be found wanting.
97
+
74
98
  ## lumo task status — the read half (self-check entry point)
75
99
 
76
100
  `lumo task status [task] [--json]` is the read-only counterpart of the loop
@@ -102,7 +126,7 @@ what's unmet and why (the exact failure tails), and how many rounds are left.
102
126
  - Header: task identifier/title/status + `verification round N/3` (round 0 =
103
127
  never verified) + an escalation warning when the machine loop is exhausted.
104
128
  - **Criteria** — every criterion as `<glyph> <id> [TYPE] SOURCE@rN
105
- statement` (✓ latest verdict passed / ✗ failed / ○ no verdict yet) with its
129
+ statement` (✓ latest verdict passed / ✗ failed / ○ no verdict yet) with its
106
130
  checkpointer and latest verdict line (evidence pointer on pass, failure
107
131
  tail on fail). `REVIEW_ADDED@rN` provenance is visible per row.
108
132
  - **History** — one line per recorded round: `rN · timestamp · X PASS / Y FAIL`.
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.program = void 0;
37
38
  const fs = __importStar(require("fs"));
38
39
  const os = __importStar(require("os"));
39
40
  const path = __importStar(require("path"));
@@ -123,9 +124,16 @@ const worktree_rm_1 = require("./commands/worktree-rm");
123
124
  const worktree_list_1 = require("./commands/worktree-list");
124
125
  const update_check_1 = require("./lib/update-check");
125
126
  const sanitize_1 = require("./lib/sanitize");
127
+ // Introspection mode: tooling (scripts/analysis grammar extraction) imports
128
+ // this module to walk the registered commander tree without running the CLI.
129
+ // Skips package.json resolution (which assumes the compiled dist/ layout),
130
+ // update checks, local-state cleanup, and parseAsync. Never set for real runs.
131
+ const isIntrospect = process.env.LUMO_CLI_INTROSPECT === '1';
126
132
  // Resolve package.json relative to __dirname so this works regardless of how
127
133
  // deep the compiled output ends up (flat dist/ or nested dist/cli/src/).
128
- const pkg = require(path.resolve(__dirname, '../../..', 'package.json'));
134
+ const pkg = isIntrospect
135
+ ? { name: '@lumoai/cli', version: '0.0.0-introspect' }
136
+ : require(path.resolve(__dirname, '../../..', 'package.json'));
129
137
  // Detached background-refresh worker: re-entry point for the spawn() in
130
138
  // maybeRefreshInBackground(). Fetches latest, writes cache, exits — skipping
131
139
  // commander.parseAsync() below.
@@ -135,7 +143,7 @@ if (isUpdateCheckWorker) {
135
143
  .catch(() => { })
136
144
  .finally(() => process.exit(0));
137
145
  }
138
- else {
146
+ else if (!isIntrospect) {
139
147
  (0, update_check_1.printUpdateNoticeIfAny)(pkg.name, pkg.version);
140
148
  (0, update_check_1.maybeRefreshInBackground)(pkg.name);
141
149
  }
@@ -143,8 +151,7 @@ else {
143
151
  // is now server-authoritative; the legacy global pointer and the
144
152
  // per-session sentinel directory are both obsolete. Failures are
145
153
  // swallowed so a permission glitch doesn't prevent CLI invocation.
146
- ;
147
- (() => {
154
+ if (!isIntrospect) {
148
155
  const dir = process.env.LUMO_CONFIG_DIR || path.join(os.homedir(), '.lumo');
149
156
  try {
150
157
  fs.rmSync(path.join(dir, 'current-task.json'), { force: true });
@@ -154,7 +161,7 @@ else {
154
161
  fs.rmSync(path.join(dir, 'sessions'), { recursive: true, force: true });
155
162
  }
156
163
  catch { }
157
- })();
164
+ }
158
165
  const collect = (val, acc) => [...acc, val];
159
166
  function wrap(fn) {
160
167
  return async (...args) => {
@@ -179,6 +186,7 @@ const program = new commander_1.Command()
179
186
  // created via .command() inherit these settings.
180
187
  .showSuggestionAfterError(true)
181
188
  .showHelpAfterError('(run the command with --help to list its valid flags and arguments)');
189
+ exports.program = program;
182
190
  const auth = program.command('auth').description('Manage Lumo authentication');
183
191
  auth
184
192
  .command('login')
@@ -883,7 +891,7 @@ for (const [slug, description] of HOOK_SUBCOMMANDS) {
883
891
  .option('--agent <token>', 'Coding agent that owns this session (e.g. claude-code, codex). Baked in by `lumo setup --agent`.')
884
892
  .action(wrap((opts) => (0, hook_1.hookCommand)(slug, opts.agent)));
885
893
  }
886
- if (!isUpdateCheckWorker) {
894
+ if (!isUpdateCheckWorker && !isIntrospect) {
887
895
  program.parseAsync(process.argv).catch(err => {
888
896
  const msg = err instanceof Error ? err.message : String(err);
889
897
  console.error(`Error: ${(0, sanitize_1.sanitizeField)(msg)}`);
@@ -1,24 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.matchTaskIdentifier = matchTaskIdentifier;
3
+ exports.matchTaskIdentifier = void 0;
4
4
  exports.extractTaskFromGit = extractTaskFromGit;
5
5
  const child_process_1 = require("child_process");
6
+ const task_identifier_1 = require("../../../shared/src/task-identifier");
7
+ Object.defineProperty(exports, "matchTaskIdentifier", { enumerable: true, get: function () { return task_identifier_1.matchTaskIdentifier; } });
6
8
  /**
7
9
  * How many recent commit subjects to scan when the branch name carries no
8
10
  * task id (e.g. on a generic feature branch or detached HEAD).
9
11
  */
10
12
  const DEFAULT_COMMIT_DEPTH = 20;
11
- const TASK_RE = /LUM-(\d+)/i;
12
- /**
13
- * Pull the first `LUM-<n>` token out of arbitrary text (a branch name or a
14
- * commit subject) and normalize it to upper case. Returns null when no task
15
- * id is present. The leading `-` in the pattern means the bare word `lumo`
16
- * never matches.
17
- */
18
- function matchTaskIdentifier(text) {
19
- const m = text.match(TASK_RE);
20
- return m ? `LUM-${m[1]}` : null;
21
- }
22
13
  /**
23
14
  * Run a git subcommand and return trimmed stdout, or '' on any failure
24
15
  * (non-zero exit, spawn error, not a repository, timeout). Never throws.
@@ -41,17 +32,18 @@ function gitOutput(args, cwd) {
41
32
  /**
42
33
  * Infer the task to work on from local git: prefer the current branch name
43
34
  * (e.g. `lumo/LUM-145-...`), then fall back to the most recent commit
44
- * subjects (e.g. `... [LUM-145]`). Returns null when nothing matches
45
- * detached HEAD (branch reads `HEAD`), a non-lumo branch with no tagged
46
- * commits, or a directory that is not a git repository all degrade to null.
35
+ * subjects (e.g. `... [LUM-145]`). Any team prefix matches (SPEC-12 as much
36
+ * as LUM-145). Returns null when nothing matches detached HEAD (branch
37
+ * reads `HEAD`), a branch with no identifier and no tagged commits, or a
38
+ * directory that is not a git repository all degrade to null.
47
39
  */
48
40
  function extractTaskFromGit(cwd, commitDepth = DEFAULT_COMMIT_DEPTH) {
49
41
  const branch = gitOutput(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
50
- const fromBranch = matchTaskIdentifier(branch);
42
+ const fromBranch = (0, task_identifier_1.matchTaskIdentifier)(branch);
51
43
  if (fromBranch)
52
44
  return { identifier: fromBranch, source: 'branch' };
53
45
  const log = gitOutput(['log', '-n', String(commitDepth), '--format=%s'], cwd);
54
- const fromLog = matchTaskIdentifier(log);
46
+ const fromLog = (0, task_identifier_1.matchTaskIdentifier)(log);
55
47
  if (fromLog)
56
48
  return { identifier: fromLog, source: 'commit' };
57
49
  return null;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TASK_IDENTIFIER_PATTERN = void 0;
4
+ exports.matchTaskIdentifier = matchTaskIdentifier;
5
+ /**
6
+ * Single source of truth for the task-identifier pattern (LUM-419).
7
+ *
8
+ * Consumed by both sides so they cannot drift:
9
+ * - server: lib/integrations/github/branch.ts (webhook branch → task linking)
10
+ * - CLI: cli/src/lib/git-task.ts (session-start git-suggest)
11
+ *
12
+ * A task identifier is `<TEAM>-<n>` where TEAM is the team's configured
13
+ * prefix (`Team.identifier`, 1–10 letters) — e.g. LUM-419, SPEC-123. The
14
+ * prefix is NOT hardcoded to any one team.
15
+ */
16
+ exports.TASK_IDENTIFIER_PATTERN = String.raw `\b([A-Za-z]{1,10}-\d+)\b`;
17
+ /**
18
+ * Acronym-number tokens that match the identifier shape but are never task
19
+ * ids. Commit subjects routinely contain these ("fix UTF-8 handling",
20
+ * "use SHA-256"), so the matcher skips them instead of suggesting a bogus
21
+ * session attach. Compared against the uppercased prefix segment.
22
+ */
23
+ const NON_TASK_PREFIXES = new Set([
24
+ 'AES',
25
+ 'CVE',
26
+ 'GPT',
27
+ 'HTTP',
28
+ 'ISO',
29
+ 'RFC',
30
+ 'RSA',
31
+ 'SHA',
32
+ 'TLS',
33
+ 'UTF',
34
+ ]);
35
+ /**
36
+ * Extract the first task identifier from arbitrary text (a branch name or a
37
+ * commit subject), normalized to upper case. Tokens whose prefix is a known
38
+ * non-task acronym (UTF-8, SHA-256, …) are skipped; later genuine matches in
39
+ * the same text still win. Returns null when nothing matches.
40
+ */
41
+ function matchTaskIdentifier(text) {
42
+ const re = new RegExp(exports.TASK_IDENTIFIER_PATTERN, 'g');
43
+ for (const m of text.matchAll(re)) {
44
+ const identifier = m[1].toUpperCase();
45
+ const prefix = identifier.slice(0, identifier.lastIndexOf('-'));
46
+ if (NON_TASK_PREFIXES.has(prefix))
47
+ continue;
48
+ return identifier;
49
+ }
50
+ return null;
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumoai/cli",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "description": "Lumo CLI — manage tasks and sessions from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "cli@uselumo.ai",