work-kit-cli 0.2.8 → 0.4.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.
Files changed (99) hide show
  1. package/README.md +24 -13
  2. package/cli/src/commands/bootstrap.test.ts +40 -0
  3. package/cli/src/commands/bootstrap.ts +77 -13
  4. package/cli/src/commands/cancel.ts +1 -16
  5. package/cli/src/commands/complete.ts +92 -98
  6. package/cli/src/commands/completions.ts +2 -2
  7. package/cli/src/commands/doctor.ts +1 -1
  8. package/cli/src/commands/extract.ts +217 -0
  9. package/cli/src/commands/init.test.ts +50 -0
  10. package/cli/src/commands/init.ts +70 -35
  11. package/cli/src/commands/learn.test.ts +217 -0
  12. package/cli/src/commands/learn.ts +104 -0
  13. package/cli/src/commands/loopback.ts +8 -11
  14. package/cli/src/commands/next.ts +93 -60
  15. package/cli/src/commands/observe.ts +16 -21
  16. package/cli/src/commands/pause-resume.test.ts +142 -0
  17. package/cli/src/commands/pause.ts +34 -0
  18. package/cli/src/commands/report.ts +217 -0
  19. package/cli/src/commands/resume.ts +126 -0
  20. package/cli/src/commands/setup.ts +280 -0
  21. package/cli/src/commands/status.ts +8 -6
  22. package/cli/src/commands/uninstall.ts +8 -3
  23. package/cli/src/commands/workflow.ts +43 -33
  24. package/cli/src/config/agent-map.ts +9 -9
  25. package/cli/src/config/constants.ts +54 -0
  26. package/cli/src/config/loopback-routes.ts +13 -13
  27. package/cli/src/config/model-routing.test.ts +190 -0
  28. package/cli/src/config/model-routing.ts +208 -0
  29. package/cli/src/config/project-config.test.ts +127 -0
  30. package/cli/src/config/project-config.ts +106 -0
  31. package/cli/src/config/{phases.ts → workflow.ts} +40 -23
  32. package/cli/src/context/prompt-builder.ts +10 -9
  33. package/cli/src/index.ts +130 -9
  34. package/cli/src/observer/data.ts +196 -65
  35. package/cli/src/observer/renderer.ts +127 -107
  36. package/cli/src/observer/watcher.ts +28 -16
  37. package/cli/src/state/helpers.test.ts +28 -28
  38. package/cli/src/state/helpers.ts +37 -25
  39. package/cli/src/state/schema.ts +135 -45
  40. package/cli/src/state/store.ts +127 -7
  41. package/cli/src/state/validators.test.ts +13 -13
  42. package/cli/src/state/validators.ts +3 -4
  43. package/cli/src/utils/colors.ts +2 -0
  44. package/cli/src/utils/fs.ts +13 -0
  45. package/cli/src/utils/json.ts +20 -0
  46. package/cli/src/utils/knowledge.ts +471 -0
  47. package/cli/src/utils/time.ts +27 -0
  48. package/cli/src/{engine → workflow}/loopbacks.test.ts +2 -2
  49. package/cli/src/workflow/loopbacks.ts +42 -0
  50. package/cli/src/workflow/parallel.ts +64 -0
  51. package/cli/src/workflow/transitions.test.ts +129 -0
  52. package/cli/src/{engine → workflow}/transitions.ts +18 -22
  53. package/package.json +2 -2
  54. package/skills/auto-kit/SKILL.md +44 -27
  55. package/skills/cancel-kit/SKILL.md +4 -4
  56. package/skills/full-kit/SKILL.md +45 -28
  57. package/skills/pause-kit/SKILL.md +25 -0
  58. package/skills/resume-kit/SKILL.md +64 -0
  59. package/skills/wk-bootstrap/SKILL.md +11 -5
  60. package/skills/wk-build/SKILL.md +12 -11
  61. package/skills/wk-build/{stages → steps}/commit.md +1 -1
  62. package/skills/wk-build/{stages → steps}/core.md +3 -3
  63. package/skills/wk-build/{stages → steps}/integration.md +2 -2
  64. package/skills/wk-build/{stages → steps}/migration.md +1 -1
  65. package/skills/wk-build/{stages → steps}/red.md +1 -1
  66. package/skills/wk-build/{stages → steps}/refactor.md +1 -1
  67. package/skills/wk-build/{stages → steps}/setup.md +1 -1
  68. package/skills/wk-build/{stages → steps}/ui.md +1 -1
  69. package/skills/wk-deploy/SKILL.md +7 -6
  70. package/skills/wk-deploy/{stages → steps}/merge.md +1 -1
  71. package/skills/wk-deploy/{stages → steps}/monitor.md +1 -1
  72. package/skills/wk-deploy/{stages → steps}/remediate.md +1 -1
  73. package/skills/wk-plan/SKILL.md +15 -14
  74. package/skills/wk-plan/{stages → steps}/architecture.md +1 -1
  75. package/skills/wk-plan/{stages → steps}/audit.md +2 -2
  76. package/skills/wk-plan/{stages → steps}/blueprint.md +2 -2
  77. package/skills/wk-plan/{stages → steps}/clarify.md +1 -1
  78. package/skills/wk-plan/{stages → steps}/investigate.md +1 -1
  79. package/skills/wk-plan/{stages → steps}/scope.md +1 -1
  80. package/skills/wk-plan/{stages → steps}/sketch.md +1 -1
  81. package/skills/wk-plan/{stages → steps}/ux-flow.md +1 -1
  82. package/skills/wk-review/SKILL.md +11 -10
  83. package/skills/wk-review/{stages → steps}/compliance.md +1 -1
  84. package/skills/wk-review/{stages → steps}/handoff.md +2 -2
  85. package/skills/wk-review/{stages → steps}/performance.md +1 -1
  86. package/skills/wk-review/{stages → steps}/security.md +1 -1
  87. package/skills/wk-review/{stages → steps}/self-review.md +1 -1
  88. package/skills/wk-test/SKILL.md +9 -8
  89. package/skills/wk-test/steps/e2e.md +56 -0
  90. package/skills/wk-test/{stages → steps}/validate.md +1 -1
  91. package/skills/wk-test/{stages → steps}/verify.md +1 -1
  92. package/skills/wk-wrap-up/SKILL.md +19 -5
  93. package/skills/wk-wrap-up/steps/knowledge.md +76 -0
  94. package/skills/wk-wrap-up/steps/summary.md +86 -0
  95. package/cli/src/engine/loopbacks.ts +0 -32
  96. package/cli/src/engine/parallel.ts +0 -60
  97. package/cli/src/engine/transitions.test.ts +0 -129
  98. package/skills/wk-test/stages/e2e.md +0 -53
  99. /package/cli/src/{engine/phases.ts → workflow/gates.ts} +0 -0
@@ -1,15 +1,17 @@
1
1
  import {
2
2
  PhaseName,
3
- SUBSTAGES_BY_PHASE,
3
+ PHASE_NAMES,
4
+ STEPS_BY_PHASE,
4
5
  Classification,
5
6
  WorkflowStep,
6
7
  } from "../state/schema.js";
8
+ import { SKILL_DIR_PREFIX } from "./constants.js";
7
9
 
8
- // ── Phase Order ──────────────────────────────────────────────────────
10
+ // ── Phase Order ─────────────────────────────────────────────────────
9
11
 
10
- export const PHASE_ORDER: PhaseName[] = ["plan", "build", "test", "review", "deploy", "wrap-up"];
12
+ export const PHASE_ORDER: PhaseName[] = [...PHASE_NAMES];
11
13
 
12
- // ── Prerequisites ────────────────────────────────────────────────────
14
+ // ── Prerequisites ───────────────────────────────────────────────────
13
15
 
14
16
  export const PHASE_PREREQUISITES: Record<PhaseName, PhaseName | null> = {
15
17
  plan: null,
@@ -20,15 +22,15 @@ export const PHASE_PREREQUISITES: Record<PhaseName, PhaseName | null> = {
20
22
  "wrap-up": "review", // or deploy if deploy was included
21
23
  };
22
24
 
23
- // ── Skill File Paths ─────────────────────────────────────────────────
25
+ // ── Skill File Paths ────────────────────────────────────────────────
24
26
 
25
- export function skillFilePath(phase: PhaseName, subStage?: string): string {
26
- const dir = `wk-${phase}`;
27
- if (!subStage) return `.claude/skills/${dir}/SKILL.md`;
28
- return `.claude/skills/${dir}/stages/${subStage}.md`;
27
+ export function skillFilePath(phase: PhaseName, step?: string): string {
28
+ const dir = `${SKILL_DIR_PREFIX}${phase}`;
29
+ if (!step) return `.claude/skills/${dir}/SKILL.md`;
30
+ return `.claude/skills/${dir}/steps/${step}.md`;
29
31
  }
30
32
 
31
- // ── Auto-kit Default Workflows ───────────────────────────────────────
33
+ // ── Auto-kit Default Workflows ──────────────────────────────────────
32
34
 
33
35
  type InclusionRule = "YES" | "skip" | "if UI" | "if DB" | "optional";
34
36
 
@@ -42,7 +44,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
42
44
  "review/self-review": "YES", "review/security": "skip", "review/performance": "skip",
43
45
  "review/compliance": "skip", "review/handoff": "YES",
44
46
  "deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
45
- "wrap-up/wrap-up": "YES",
47
+ "wrap-up/summary": "YES", "wrap-up/knowledge": "skip",
46
48
  },
47
49
  "small-change": {
48
50
  "plan/clarify": "YES", "plan/investigate": "skip", "plan/sketch": "skip", "plan/scope": "skip",
@@ -53,7 +55,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
53
55
  "review/self-review": "YES", "review/security": "skip", "review/performance": "skip",
54
56
  "review/compliance": "skip", "review/handoff": "YES",
55
57
  "deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
56
- "wrap-up/wrap-up": "YES",
58
+ "wrap-up/summary": "YES", "wrap-up/knowledge": "skip",
57
59
  },
58
60
  refactor: {
59
61
  "plan/clarify": "YES", "plan/investigate": "YES", "plan/sketch": "skip", "plan/scope": "skip",
@@ -64,7 +66,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
64
66
  "review/self-review": "YES", "review/security": "skip", "review/performance": "YES",
65
67
  "review/compliance": "skip", "review/handoff": "YES",
66
68
  "deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
67
- "wrap-up/wrap-up": "YES",
69
+ "wrap-up/summary": "YES", "wrap-up/knowledge": "YES",
68
70
  },
69
71
  feature: {
70
72
  "plan/clarify": "YES", "plan/investigate": "YES", "plan/sketch": "YES", "plan/scope": "YES",
@@ -75,7 +77,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
75
77
  "review/self-review": "YES", "review/security": "YES", "review/performance": "skip",
76
78
  "review/compliance": "YES", "review/handoff": "YES",
77
79
  "deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
78
- "wrap-up/wrap-up": "YES",
80
+ "wrap-up/summary": "YES", "wrap-up/knowledge": "YES",
79
81
  },
80
82
  "large-feature": {
81
83
  "plan/clarify": "YES", "plan/investigate": "YES", "plan/sketch": "YES", "plan/scope": "YES",
@@ -86,20 +88,35 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
86
88
  "review/self-review": "YES", "review/security": "YES", "review/performance": "YES",
87
89
  "review/compliance": "YES", "review/handoff": "YES",
88
90
  "deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
89
- "wrap-up/wrap-up": "YES",
91
+ "wrap-up/summary": "YES", "wrap-up/knowledge": "YES",
90
92
  },
91
93
  };
92
94
 
93
- export function buildDefaultWorkflow(classification: Classification): WorkflowStep[] {
95
+ export function buildDefaultWorkflow(
96
+ classification: Classification,
97
+ overrides?: { include?: string[]; exclude?: string[] }
98
+ ): WorkflowStep[] {
94
99
  const matrix = WORKFLOW_MATRIX[classification];
95
100
  const steps: WorkflowStep[] = [];
96
101
 
102
+ const forceInclude = new Set(overrides?.include ?? []);
103
+ const forceExclude = new Set(overrides?.exclude ?? []);
104
+
97
105
  for (const [key, rule] of Object.entries(matrix)) {
98
- const [phase, subStage] = key.split("/") as [PhaseName, string];
99
- // "YES" always included, "skip" excluded, conditional ones included by default (user can remove)
100
- const included = rule === "YES" || rule === "if UI" || rule === "if DB";
101
- if (rule !== "skip") {
102
- steps.push({ phase, subStage, included });
106
+ const [phase, step] = key.split("/") as [PhaseName, string];
107
+ let included = rule === "YES" || rule === "if UI" || rule === "if DB";
108
+ if (forceInclude.has(key)) included = true;
109
+ if (forceExclude.has(key)) included = false;
110
+ // "skip" excluded by default, but project config may force-include
111
+ if (rule === "skip" && !forceInclude.has(key)) continue;
112
+ steps.push({ phase, step, included });
113
+ }
114
+
115
+ // Add any force-included steps not in the matrix
116
+ for (const ref of forceInclude) {
117
+ const [phase, step] = ref.split("/") as [PhaseName, string];
118
+ if (!steps.some(s => s.phase === phase && s.step === step)) {
119
+ steps.push({ phase, step, included: true });
103
120
  }
104
121
  }
105
122
 
@@ -109,8 +126,8 @@ export function buildDefaultWorkflow(classification: Classification): WorkflowSt
109
126
  export function buildFullWorkflow(): WorkflowStep[] {
110
127
  const steps: WorkflowStep[] = [];
111
128
  for (const phase of PHASE_ORDER) {
112
- for (const subStage of SUBSTAGES_BY_PHASE[phase]) {
113
- steps.push({ phase, subStage, included: true });
129
+ for (const step of STEPS_BY_PHASE[phase]) {
130
+ steps.push({ phase, step, included: true });
114
131
  }
115
132
  }
116
133
  return steps;
@@ -2,27 +2,28 @@ import { WorkKitState, PhaseName } from "../state/schema.js";
2
2
  import { getContextFor } from "../config/agent-map.js";
3
3
  import { extractSection, extractTopSection } from "./extractor.js";
4
4
  import { readStateMd } from "../state/store.js";
5
- import { skillFilePath } from "../config/phases.js";
5
+ import { skillFilePath } from "../config/workflow.js";
6
6
  import { redactIgnoredBlocks } from "./redactor.js";
7
+ import { CLI_BINARY } from "../config/constants.js";
7
8
 
8
9
  /**
9
- * Build a complete agent prompt for a given phase/sub-stage.
10
+ * Build a complete agent prompt for a given phase/step.
10
11
  * Accepts optional pre-read stateMd to avoid repeated file reads in parallel scenarios.
11
12
  */
12
13
  export function buildAgentPrompt(
13
14
  worktreeRoot: string,
14
15
  state: WorkKitState,
15
16
  phase: PhaseName,
16
- subStage: string,
17
+ step: string,
17
18
  stateMd?: string | null
18
19
  ): string {
19
- const ctx = getContextFor(phase, subStage);
20
+ const ctx = getContextFor(phase, step);
20
21
  const md = stateMd ?? readStateMd(worktreeRoot);
21
- const skill = skillFilePath(phase, subStage);
22
+ const skill = skillFilePath(phase, step);
22
23
 
23
24
  const parts: string[] = [];
24
25
 
25
- parts.push(`# Agent: ${phase}/${subStage}`);
26
+ parts.push(`# Agent: ${phase}/${step}`);
26
27
  parts.push(`**Worktree:** ${worktreeRoot}`);
27
28
  parts.push(`**Slug:** ${state.slug}`);
28
29
  parts.push(`**Branch:** ${state.branch}`);
@@ -59,12 +60,12 @@ export function buildAgentPrompt(
59
60
  }
60
61
 
61
62
  parts.push(`## Output`);
62
- parts.push(`Write your outputs to \`.work-kit/state.md\` under a section for this sub-stage.`);
63
+ parts.push(`Write your outputs to \`.work-kit/state.md\` under a section for this step.`);
63
64
 
64
- if (subStage === "wrap-up") {
65
+ if (step === "wrap-up") {
65
66
  parts.push(`Follow the wrap-up skill file instructions for archiving and cleanup.`);
66
67
  } else {
67
- parts.push(`When done, report your outcome so the orchestrator can run: \`npx work-kit-cli complete ${phase}/${subStage} --outcome <outcome>\``);
68
+ parts.push(`When done, report your outcome so the orchestrator can run: \`${CLI_BINARY} complete ${phase}/${step} --outcome <outcome>\``);
68
69
  }
69
70
 
70
71
  return redactIgnoredBlocks(parts.join("\n"));
package/cli/src/index.ts CHANGED
@@ -17,8 +17,14 @@ import { observeCommand } from "./commands/observe.js";
17
17
  import { uninstallCommand } from "./commands/uninstall.js";
18
18
  import { bootstrapCommand } from "./commands/bootstrap.js";
19
19
  import { cancelCommand } from "./commands/cancel.js";
20
+ import { pauseCommand } from "./commands/pause.js";
21
+ import { resumeCommand } from "./commands/resume.js";
22
+ import { reportCommand } from "./commands/report.js";
23
+ import { learnCommand } from "./commands/learn.js";
24
+ import { extractCommand } from "./commands/extract.js";
20
25
  import { bold, green, yellow, red } from "./utils/colors.js";
21
- import type { Classification, PhaseName } from "./state/schema.js";
26
+ import type { Classification, ModelPolicy, PhaseName } from "./state/schema.js";
27
+ import { isModelPolicy } from "./state/schema.js";
22
28
 
23
29
  import { createRequire } from "node:module";
24
30
 
@@ -37,18 +43,27 @@ program
37
43
  program
38
44
  .command("init")
39
45
  .description("Create worktree and initialize state")
40
- .requiredOption("--mode <mode>", "Workflow mode: full or auto")
46
+ .option("--mode <mode>", "Workflow mode: full or auto (default: from project config or 'full')")
41
47
  .requiredOption("--description <text>", "Description of the work")
42
48
  .option("--classification <type>", "Work classification (auto mode): bug-fix, small-change, refactor, feature, large-feature")
43
49
  .option("--gated", "Wait for user approval between phases (default: auto-proceed)")
50
+ .option("--model-policy <policy>", "Session model policy: auto, opus, sonnet, haiku, inherit (default: auto)")
44
51
  .option("--worktree-root <path>", "Override worktree root directory")
45
52
  .action((opts) => {
46
53
  try {
54
+ if (opts.modelPolicy !== undefined && !isModelPolicy(opts.modelPolicy)) {
55
+ console.error(JSON.stringify({
56
+ action: "error",
57
+ message: `Invalid --model-policy "${opts.modelPolicy}". Use one of: auto, opus, sonnet, haiku, inherit.`,
58
+ }));
59
+ process.exit(1);
60
+ }
47
61
  const result = initCommand({
48
- mode: opts.mode as "full" | "auto",
62
+ mode: opts.mode as "full" | "auto" | undefined,
49
63
  description: opts.description,
50
64
  classification: opts.classification as Classification | undefined,
51
- gated: opts.gated || false,
65
+ gated: opts.gated,
66
+ modelPolicy: opts.modelPolicy as ModelPolicy | undefined,
52
67
  worktreeRoot: opts.worktreeRoot,
53
68
  });
54
69
  console.log(JSON.stringify(result, null, 2));
@@ -78,7 +93,7 @@ program
78
93
 
79
94
  program
80
95
  .command("complete <target>")
81
- .description("Mark a phase/sub-stage as complete (e.g., plan/clarify)")
96
+ .description("Mark a phase/step as complete (e.g., plan/clarify)")
82
97
  .option("--outcome <value>", "Outcome of the step (e.g., done, revise, broken, changes_requested)")
83
98
  .option("--worktree-root <path>", "Override worktree root")
84
99
  .action((target, opts) => {
@@ -144,8 +159,8 @@ program
144
159
  program
145
160
  .command("loopback")
146
161
  .description("Register a loop-back transition")
147
- .requiredOption("--from <source>", "Source phase/sub-stage (e.g., review/handoff)")
148
- .requiredOption("--to <target>", "Target phase/sub-stage (e.g., build/core)")
162
+ .requiredOption("--from <source>", "Source phase/step (e.g., review/handoff)")
163
+ .requiredOption("--to <target>", "Target phase/step (e.g., build/core)")
149
164
  .requiredOption("--reason <text>", "Reason for loop-back")
150
165
  .option("--worktree-root <path>", "Override worktree root")
151
166
  .action((opts) => {
@@ -241,8 +256,9 @@ program
241
256
  .command("observe")
242
257
  .description("Real-time dashboard of all active work items")
243
258
  .option("--repo <path>", "Main repository root")
259
+ .option("--all", "Observe every work-kit project found under ~/.claude/projects/")
244
260
  .action(async (opts) => {
245
- await observeCommand({ mainRepo: opts.repo });
261
+ await observeCommand({ mainRepo: opts.repo, all: opts.all });
246
262
  });
247
263
 
248
264
  // ── uninstall ────────────────────────────────────────────────────────
@@ -260,9 +276,10 @@ program
260
276
  .command("bootstrap")
261
277
  .description("Detect work-kit state and output session orientation")
262
278
  .option("--json", "Output as JSON", true)
279
+ .option("--auto-resume", "If paused or stale, auto-flip to in-progress")
263
280
  .action((opts) => {
264
281
  try {
265
- const result = bootstrapCommand();
282
+ const result = bootstrapCommand(undefined, { autoResume: opts.autoResume });
266
283
  console.log(JSON.stringify(result, null, 2));
267
284
  } catch (e: any) {
268
285
  console.error(JSON.stringify({ action: "error", message: e.message }));
@@ -270,6 +287,59 @@ program
270
287
  }
271
288
  });
272
289
 
290
+ // ── pause ───────────────────────────────────────────────────────────
291
+
292
+ program
293
+ .command("pause")
294
+ .description("Pause the active work-kit session")
295
+ .option("--reason <text>", "Optional reason for pausing")
296
+ .option("--worktree-root <path>", "Override worktree root")
297
+ .action((opts) => {
298
+ try {
299
+ const result = pauseCommand(opts.reason, opts.worktreeRoot);
300
+ console.log(JSON.stringify(result, null, 2));
301
+ process.exit(result.action === "error" ? 1 : 0);
302
+ } catch (e: any) {
303
+ console.error(JSON.stringify({ action: "error", message: e.message }));
304
+ process.exit(1);
305
+ }
306
+ });
307
+
308
+ // ── resume ──────────────────────────────────────────────────────────
309
+
310
+ program
311
+ .command("resume")
312
+ .description("Resume a paused work-kit session (lists paused sessions when no slug is given)")
313
+ .option("--worktree-root <path>", "Override worktree root (resume session at exact path)")
314
+ .option("--slug <slug>", "Resume the paused session with this slug")
315
+ .action((opts) => {
316
+ try {
317
+ const result = resumeCommand({ worktreeRoot: opts.worktreeRoot, slug: opts.slug });
318
+ console.log(JSON.stringify(result, null, 2));
319
+ process.exit(result.action === "error" ? 1 : 0);
320
+ } catch (e: any) {
321
+ console.error(JSON.stringify({ action: "error", message: e.message }));
322
+ process.exit(1);
323
+ }
324
+ });
325
+
326
+ // ── report ──────────────────────────────────────────────────────────
327
+
328
+ program
329
+ .command("report")
330
+ .description("Show stats across completed work-kits")
331
+ .option("--json", "Output as JSON")
332
+ .option("--repo <path>", "Main repository root")
333
+ .option("--worktree-root <path>", "Override worktree root")
334
+ .action((opts) => {
335
+ try {
336
+ reportCommand({ json: opts.json, repo: opts.repo, worktreeRoot: opts.worktreeRoot });
337
+ } catch (e: any) {
338
+ console.error(JSON.stringify({ action: "error", message: e.message }));
339
+ process.exit(1);
340
+ }
341
+ });
342
+
273
343
  // ── cancel ──────────────────────────────────────────────────────────
274
344
 
275
345
  program
@@ -287,4 +357,55 @@ program
287
357
  }
288
358
  });
289
359
 
360
+ // ── learn ───────────────────────────────────────────────────────────
361
+
362
+ program
363
+ .command("learn")
364
+ .description("Append a knowledge entry (lesson/convention/risk/workflow) to .work-kit-knowledge/")
365
+ .requiredOption("--type <type>", "Entry type: lesson, convention, risk, workflow")
366
+ .requiredOption("--text <text>", "Free-form text. Secrets are auto-redacted at write time.")
367
+ .option("--scope <glob>", "Optional path glob (stored, not yet used for filtering)")
368
+ .option("--phase <phase>", "Override session phase auto-fill")
369
+ .option("--step <step>", "Override session step auto-fill")
370
+ .option("--source <source>", "Override entry source label", "explicit-cli")
371
+ .option("--worktree-root <path>", "Override worktree root")
372
+ .action((opts) => {
373
+ try {
374
+ const result = learnCommand({
375
+ type: opts.type,
376
+ text: opts.text,
377
+ scope: opts.scope,
378
+ phase: opts.phase,
379
+ step: opts.step,
380
+ source: opts.source,
381
+ worktreeRoot: opts.worktreeRoot,
382
+ });
383
+ console.log(JSON.stringify(result, null, 2));
384
+ if (result.action === "error") process.exit(1);
385
+ if (result.redacted) {
386
+ console.error(yellow(`! Redacted ${result.redactedKinds?.length ?? 0} secret(s) before writing.`));
387
+ }
388
+ } catch (e: any) {
389
+ console.error(JSON.stringify({ action: "error", message: e.message }));
390
+ process.exit(1);
391
+ }
392
+ });
393
+
394
+ // ── extract ─────────────────────────────────────────────────────────
395
+
396
+ program
397
+ .command("extract")
398
+ .description("Parse current session's state.md + tracker.json and append entries to knowledge files")
399
+ .option("--worktree-root <path>", "Override worktree root")
400
+ .action((opts) => {
401
+ try {
402
+ const result = extractCommand({ worktreeRoot: opts.worktreeRoot });
403
+ console.log(JSON.stringify(result, null, 2));
404
+ if (result.action === "error") process.exit(1);
405
+ } catch (e: any) {
406
+ console.error(JSON.stringify({ action: "error", message: e.message }));
407
+ process.exit(1);
408
+ }
409
+ });
410
+
290
411
  program.parse();