@delete101020/aidlc 4.0.0 → 4.0.2

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 (3) hide show
  1. package/README.md +62 -39
  2. package/dist/bundle.js +268 -101
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,36 +3,32 @@
3
3
  [![License: MIT](https://img.shields.io/badge/license-MIT-97ca00)](https://github.com/delete101020/ai-native-sdlc/blob/main/LICENSE)
4
4
  [![npm](https://img.shields.io/npm/v/@delete101020/aidlc)](https://www.npmjs.com/package/@delete101020/aidlc)
5
5
 
6
- Terminal CLI for AIDLC — drives Claude through pipelines you declare in
7
- `.aidlc/workspace.yaml`. Manages the workspace, executes runs end-to-end via
8
- the `claude` CLI, and shares state with the VS Code extension over the
9
- filesystem (no daemon, no IPC).
6
+ The terminal side of **AIDLC Native**: drive Claude through the six stages of the
7
+ [AI-Native SDLC Playbook](https://claude.com/blog/the-ai-native-sdlc-playbook) —
8
+ **Plan → Design → Build → Test → Deploy → Maintain** — from any shell, a CI job or
9
+ a cron script. Every stage is an agent that writes one artifact (`intent.md`,
10
+ `spec.md`, `plan.md`, `verify.md`, `review.md`, `incident.md`), and the
11
+ pipeline waits at the review gates you keep.
10
12
 
11
- **Claude only.** The CLI shells out to `claude --print --append-system-prompt
12
- <skill>`. No Anthropic SDK calls, no other model runners.
13
+ The CLI shares `.aidlc/workspace.yaml` and the run files with the
14
+ [VS Code extension](https://marketplace.visualstudio.com/items?itemName=delete101020.aidlc-native)
15
+ (no daemon, no IPC), so an epic started in one can be finished in the other.
13
16
 
14
- ### New in 0.8
17
+ **Claude only.** The CLI shells out to `claude --print --append-system-prompt
18
+ <skill>`. No Anthropic SDK calls.
15
19
 
16
- `aidlc globals install` now also installs the **artifact annotation** tooling under
17
- `~/.claude` — a zero-dependency Markdown→HTML renderer, a vendored
18
- [annotron](https://www.npmjs.com/package/annotron) review editor (no global install), a
19
- per-epic **memory** tool, and the `/annotate-artifact` + `/epic-context` skills. Review epic
20
- artifacts in a browser and apply feedback back to the Markdown — from a plain terminal +
21
- Claude Code, no VS Code required. Optional `aidlc globals memory-hook enable` auto-loads an
22
- epic's memory whenever a prompt mentions it. See the [`globals`](#globals--built-in-workflow-agents--skills-under-claude) command.
20
+ > **A fork** of [`aidlc-io/aidlc`](https://github.com/aidlc-io/aidlc) by hueanmy, not
21
+ > affiliated with the upstream author. The package is scoped because the upstream
22
+ > owns `aidlc` on npm; the command it installs is still `aidlc`.
23
23
 
24
24
  ## Install
25
25
 
26
- The CLI of **AIDLC Native**, a fork of [`aidlc-io/aidlc`](https://github.com/aidlc-io/aidlc)
27
- (not affiliated with the upstream author). The package is scoped because the
28
- upstream owns `aidlc` on npm; the command it installs is still `aidlc`:
29
-
30
26
  ```sh
31
27
  npm install -g @delete101020/aidlc
32
28
  aidlc --version
33
29
  ```
34
30
 
35
- If the upstream `aidlc` package is installed globally, `npm uninstall -g aidlc`
31
+ If the upstream `aidlc` package is installed globally, run `npm uninstall -g aidlc`
36
32
  first — both provide the `aidlc` command.
37
33
 
38
34
  To run unreleased changes, build it from the repo:
@@ -41,7 +37,6 @@ To run unreleased changes, build it from the repo:
41
37
  pnpm install # at repo root
42
38
  pnpm -r compile
43
39
  cd packages/cli && pnpm bundle && npm link # makes `aidlc` available globally
44
- aidlc --version # 3.5.1
45
40
  ```
46
41
 
47
42
  `npm link` points at the working tree, so a later `pnpm bundle` is enough to
@@ -60,20 +55,36 @@ Run `aidlc doctor` to verify all of the above.
60
55
  ## Five-minute walkthrough
61
56
 
62
57
  ```sh
63
- # 1. New workspace from scratch
64
- mkdir my-pipeline && cd my-pipeline
58
+ # 1. A workspace with the AI-Native SDLC workflow
59
+ cd my-project
65
60
  aidlc init
61
+ aidlc preset apply ai-native # 6 agents, 7 skills, the ai-native-full pipeline
66
62
  aidlc doctor # confirm claude is wired up
67
63
 
68
- # 2. Drop in a built-in preset (or build manually with skill/agent/pipeline add)
69
- aidlc preset apply code-review
70
- aidlc list # see the agents, skills, pipeline you got
64
+ # 2. Start an epic — the brief picks a recipe (here: native-fix)
65
+ aidlc epic start EPIC-1 --brief "fix the login redirect loop"
71
66
 
72
- # 3. Kick off a run and let Claude do the work
73
- aidlc run start review-pipeline --context diff=$(git diff HEAD~1)
74
- aidlc run exec <runId> # streams claude output, advances on success
67
+ # 3. Let Claude work it, stopping at each review gate
68
+ aidlc run exec EPIC-1 # exit 2 = waiting for your review
69
+ aidlc run approve EPIC-1 # or: aidlc run reject EPIC-1 --reason "…"
70
+ aidlc run exec EPIC-1 # carries on to the next gate
75
71
  ```
76
72
 
73
+ Recipes right-size the pipeline to the task:
74
+
75
+ | Recipe | Steps | For |
76
+ |---|---|---|
77
+ | `native-full` | intent → spec → build-plan → implement → verify → review | New behaviour |
78
+ | `native-fix` | intent → build-plan → implement → verify → review | Bugs, refactors, tech debt |
79
+ | `native-lite` | intent → build-plan → implement → review | Small changes with a precedent in the code |
80
+ | `native-quick` | intent → build-plan → implement → verify | Small, well-understood changes |
81
+ | `native-align` | intent → spec | Agreeing on scope before any code |
82
+ | `native-hotfix` | build-plan → implement → review | Production is down and the cause is known |
83
+
84
+ Pick one yourself with `aidlc epic start <id> --recipe native-full`, or list them
85
+ with `aidlc pipeline recipes`. Stage 6 has its own entry point — see
86
+ [`maintain`](#maintain--stage-6-the-ai-native-return-path).
87
+
77
88
  ## Command reference
78
89
 
79
90
  Global flags available on every subcommand:
@@ -125,15 +136,16 @@ run-state JSON files are parseable. Exit 1 on any failure (including skill /
125
136
  runner / runtime checks). `--json` emits every section as
126
137
  `{ ok, failures, sections }` — a parseable CI preflight.
127
138
 
128
- ### `mcp` — give another CLI the ast-graph server
139
+ ### `mcp` — give another CLI the code-graph server
129
140
 
130
141
  ```
131
142
  aidlc mcp status
132
143
  aidlc mcp register [--runner codex] [--dry-run]
133
144
  ```
134
145
 
135
- The VS Code extension registers the `ast-graph` MCP server with Claude
136
- automatically, project-scoped. Codex keeps MCP servers in `~/.codex/config.toml`,
146
+ The VS Code extension registers a code-graph MCP server with Claude
147
+ automatically, project-scoped — `ast-graph` by default, or `codegraph` when
148
+ `aidlcNative.astGraph.engine` is set to it. Codex keeps MCP servers in `~/.codex/config.toml`,
137
149
  which is per-user: registering there points every Codex session on the machine
138
150
  at this workspace's graph, so it stays an explicit command. `register` copies
139
151
  the binary and db path out of the registration Claude already has — run
@@ -255,8 +267,9 @@ aidlc preset apply <name> # merges into current workspace (no ov
255
267
  aidlc preset save <name> # snapshot current workspace to .aidlc/presets/<name>.json
256
268
  ```
257
269
 
258
- Built-in presets: `code-review`, `release-notes`, `sdlc` (full 9-phase SDLC
259
- pipeline ported from the legacy AIDLC).
270
+ Built-in presets: `ai-native` (the six-stage AI-Native SDLC workflow),
271
+ `sdlc` (the classic AIDLC pipeline: Plan → (Design ∥ Test Plan) →
272
+ Implement ∥ Generate Test Cases → Execute Test), `code-review` and `release-notes` (single-agent pipelines).
260
273
 
261
274
  ### Epics
262
275
 
@@ -265,14 +278,24 @@ under whatever `state.root` your `workspace.yaml` declares (default
265
278
  `docs/epics/`).
266
279
 
267
280
  ```
268
- aidlc epic list [--status pending|in_progress|done|failed] [--json]
281
+ aidlc epic list [--status pending|in_progress|done|failed] [--tag <tag>…] [--json]
269
282
  aidlc epic status <id> [--json] # phase-by-phase view
270
283
  aidlc epic show <id> # alias for status
271
284
  aidlc epic start <id> --brief "…" [--llm] # classify the brief → recipe → assembled pipeline
272
285
  aidlc epic start <id> --recipe <recipeId> # or pick the recipe yourself
273
286
  aidlc epic start <id> --pipeline <pipelineId> # or use an existing pipeline as-is
287
+ aidlc epic start <id> … --tag payment --tag "thanh toán" # tag it at creation
288
+ aidlc epic tag <id> # print this epic's tags
289
+ aidlc epic tag <id> payment "release q3" # add tags (also --add/--remove/--set/--clear)
274
290
  ```
275
291
 
292
+ **Tags** are free text in and one canonical form on disk: uppercase, ASCII,
293
+ dash-separated — `"thanh toán VNPay"` is stored as `THANH-TOAN-VNPAY`, and the
294
+ accents and case are folded on the way *in* to a filter too, so `--tag "thanh
295
+ toán"` finds it. Repeating `--tag` narrows: an epic must carry every tag given.
296
+ They live under `tags` in the epic's `state.json` and are shown, and editable,
297
+ on each card in the VS Code panel.
298
+
276
299
  `epic start` mirrors the extension's smart **Start Epic**: `--brief` classifies
277
300
  the requirement into a task-type recipe (heuristic by default, `--llm` for
278
301
  model-backed classification) and assembles a right-sized pipeline.
@@ -581,12 +604,12 @@ still needed by other globally-installed workflows.
581
604
 
582
605
  ## Recipes
583
606
 
584
- ### Drive a complete SDLC pipeline end-to-end
607
+ ### Drive an AI-Native epic end-to-end, unattended
585
608
 
586
609
  ```sh
587
- aidlc preset apply sdlc
588
- aidlc run start sdlc-pipeline --id ABC-123 --context epic=ABC-123
589
- aidlc run exec ABC-123 --auto-approve # claude works through every phase
610
+ aidlc preset apply ai-native
611
+ aidlc epic start EPIC-7 --recipe native-full
612
+ aidlc run exec EPIC-7 --auto-approve # clears human gates without pausing
590
613
  ```
591
614
 
592
615
  ### Manually mark a phase done that you completed outside AIDLC
@@ -622,7 +645,7 @@ aidlc epic list --json | jq '.[] | select(.status=="in_progress") | .id'
622
645
 
623
646
  ```sh
624
647
  # Terminal 1 — kick off the run, then walk away
625
- aidlc run start sdlc-pipeline --id ABC-123 --context epic=ABC-123
648
+ aidlc epic start ABC-123 --brief "…"
626
649
  aidlc run exec ABC-123 --auto-approve
627
650
 
628
651
  # Terminal 2 — live table
package/dist/bundle.js CHANGED
@@ -24601,6 +24601,64 @@ var require_EpicPipelineStore = __commonJS({
24601
24601
  }
24602
24602
  });
24603
24603
 
24604
+ // ../core/dist/loader/epicTags.js
24605
+ var require_epicTags = __commonJS({
24606
+ "../core/dist/loader/epicTags.js"(exports2) {
24607
+ "use strict";
24608
+ Object.defineProperty(exports2, "__esModule", { value: true });
24609
+ exports2.MAX_TAG_LENGTH = exports2.EPIC_TAGS_KEY = void 0;
24610
+ exports2.normalizeTag = normalizeTag;
24611
+ exports2.normalizeTags = normalizeTags2;
24612
+ exports2.readEpicTags = readEpicTags3;
24613
+ exports2.epicMatchesTags = epicMatchesTags2;
24614
+ exports2.applyTagEdit = applyTagEdit2;
24615
+ exports2.EPIC_TAGS_KEY = "tags";
24616
+ exports2.MAX_TAG_LENGTH = 48;
24617
+ function normalizeTag(raw) {
24618
+ if (typeof raw !== "string") {
24619
+ return "";
24620
+ }
24621
+ return raw.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[đĐ]/g, "d").toUpperCase().replace(/[^A-Z0-9_]+/g, "-").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, exports2.MAX_TAG_LENGTH).replace(/[-_]+$/g, "");
24622
+ }
24623
+ function normalizeTags2(raw) {
24624
+ const parts = Array.isArray(raw) ? raw : typeof raw === "string" ? raw.split(/[,\n]/) : [];
24625
+ const out2 = /* @__PURE__ */ new Set();
24626
+ for (const part of parts) {
24627
+ const tag = normalizeTag(part);
24628
+ if (tag) {
24629
+ out2.add(tag);
24630
+ }
24631
+ }
24632
+ return [...out2].sort();
24633
+ }
24634
+ function readEpicTags3(parsed) {
24635
+ if (!parsed || typeof parsed !== "object") {
24636
+ return [];
24637
+ }
24638
+ return normalizeTags2(parsed[exports2.EPIC_TAGS_KEY]);
24639
+ }
24640
+ function epicMatchesTags2(epicTags, filter) {
24641
+ const wanted = normalizeTags2(filter);
24642
+ if (wanted.length === 0) {
24643
+ return true;
24644
+ }
24645
+ const have = new Set(normalizeTags2(epicTags));
24646
+ return wanted.every((t) => have.has(t));
24647
+ }
24648
+ function applyTagEdit2(current, edit) {
24649
+ const base = edit.set !== void 0 ? normalizeTags2(edit.set) : normalizeTags2(current);
24650
+ const next = new Set(base);
24651
+ for (const t of normalizeTags2(edit.add)) {
24652
+ next.add(t);
24653
+ }
24654
+ for (const t of normalizeTags2(edit.remove)) {
24655
+ next.delete(t);
24656
+ }
24657
+ return [...next].sort();
24658
+ }
24659
+ }
24660
+ });
24661
+
24604
24662
  // ../core/dist/epics/ContextCollector.js
24605
24663
  var require_ContextCollector = __commonJS({
24606
24664
  "../core/dist/epics/ContextCollector.js"(exports2) {
@@ -24997,6 +25055,7 @@ var require_EpicScaffold = __commonJS({
24997
25055
  var PipelineRunner_1 = require_PipelineRunner();
24998
25056
  var RunStateStore_1 = require_RunStateStore();
24999
25057
  var EpicPipelineStore_1 = require_EpicPipelineStore();
25058
+ var epicTags_1 = require_epicTags();
25000
25059
  var ContextCollector_1 = require_ContextCollector();
25001
25060
  var PlanGenerator_1 = require_PlanGenerator();
25002
25061
  var EpicScaffoldError3 = class extends Error {
@@ -25071,7 +25130,7 @@ var require_EpicScaffold = __commonJS({
25071
25130
  fs14.writeFileSync(stateFile, JSON.stringify(next, null, 2) + "\n", "utf8");
25072
25131
  }
25073
25132
  function scaffoldEpic2(args) {
25074
- const { workspaceRoot, doc, epicId, title, description, target, agents, inputs, extraProjects, pipeline, seedArtifacts, enableAutopilot = false, strictMode = true } = args;
25133
+ const { workspaceRoot, doc, epicId, title, description, target, agents, inputs, extraProjects, pipeline, seedArtifacts, enableAutopilot = false, strictMode = true, tags } = args;
25075
25134
  if (!epicId.trim()) {
25076
25135
  throw new EpicScaffoldError3("Epic id is required.");
25077
25136
  }
@@ -25107,6 +25166,9 @@ var require_EpicScaffold = __commonJS({
25107
25166
  // because the point of a per-epic knob is that it can be found and flipped
25108
25167
  // by hand on an epic that turns out bigger or smaller than it looked.
25109
25168
  strict_mode: strictMode,
25169
+ // Written even when empty, for the same reason as `strict_mode`: a key that
25170
+ // is there is a key someone can find and fill in by hand.
25171
+ [epicTags_1.EPIC_TAGS_KEY]: (0, epicTags_1.normalizeTags)(tags),
25110
25172
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
25111
25173
  stepStates: agents.map((a) => ({
25112
25174
  agent: a,
@@ -31818,7 +31880,7 @@ AIDLC is an AI-driven SDLC + agent workflow runner. It drives Claude through a
31818
31880
  pipeline you declare in \`.aidlc/workspace.yaml\`, tracking every run, step, and
31819
31881
  token. It has two surfaces that share the same files on disk (no daemon):
31820
31882
 
31821
- - **VS Code extension** \u2014 **AIDLC Native** (\`delete101020.aidlc\`, on the VS Code
31883
+ - **VS Code extension** \u2014 **AIDLC Native** (\`delete101020.aidlc-native\`, on the VS Code
31822
31884
  Marketplace and Open VSX; commands and settings under \`aidlcNative.*\`) \u2014 a
31823
31885
  visual Builder for workspace.yaml,
31824
31886
  a sidebar launcher, epic/run tracking, and the **AIDLC Monitor** (token usage,
@@ -31866,7 +31928,7 @@ the other within ~200ms.
31866
31928
  - Bootstrap: \`aidlc init\`, \`aidlc validate\`, \`aidlc doctor\`, \`aidlc list [--json]\`.
31867
31929
  - Config (mirrors the Builder): \`aidlc skill|agent|pipeline add|list|show|remove\`.
31868
31930
  - Presets: \`aidlc preset apply|save|list\` (built-ins code-review, release-notes, sdlc).
31869
- - Epics: \`aidlc epic list|status <id>|start <id> --brief "\u2026" [--llm]\`.
31931
+ - Epics: \`aidlc epic list [--tag <tag>]|status <id>|start <id> --brief "\u2026" [--llm]|tag <id> [tags\u2026]\`.
31870
31932
  - Recipes: \`aidlc recipe init\`, \`aidlc pipeline recipes|classify "<brief>"|generate\`.
31871
31933
  - Runs: \`aidlc run start <pipeline> --context k=v\`, \`aidlc run exec <runId> [--auto-approve]\`.
31872
31934
  - Watch: \`aidlc watch\`, \`aidlc tail\`, \`aidlc dashboard\`, \`aidlc monitor [--start|--open]\`.
@@ -31876,7 +31938,7 @@ the other within ~200ms.
31876
31938
 
31877
31939
  ## VS Code extension setup & UI
31878
31940
  1. Install **AIDLC Native** from the Extensions view (Marketplace in VS Code, Open VSX
31879
- in Antigravity / Cursor / VSCodium), or \`code --install-extension delete101020.aidlc\`.
31941
+ in Antigravity / Cursor / VSCodium), or \`code --install-extension delete101020.aidlc-native\`.
31880
31942
  CLI: \`npm install -g @delete101020/aidlc\` (the command is \`aidlc\`).
31881
31943
  2. Ensure \`claude\` is on PATH and authenticated (the sidebar surfaces MCP/claude state).
31882
31944
  3. Open a project folder. The **AIDLC** sidebar (activity bar) shows:
@@ -31949,7 +32011,8 @@ VS Code extension over the filesystem.
31949
32011
 
31950
32012
  \u2500\u2500 Step 4: Work by epic (task-type \u2192 pipeline) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
31951
32013
  aidlc epic start <id> --brief "\u2026" [--llm]
31952
- aidlc epic list [--status \u2026] aidlc epic status <id>
32014
+ aidlc epic list [--status \u2026] [--tag \u2026] aidlc epic status <id>
32015
+ aidlc epic tag <id> [tags\u2026] tag an epic (stored uppercase)
31953
32016
 
31954
32017
  \u2500\u2500 Step 5: Watch what's happening \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
31955
32018
  aidlc watch live table of all runs
@@ -31973,10 +32036,10 @@ var require_dist = __commonJS({
31973
32036
  Object.defineProperty(exports2, "__esModule", { value: true });
31974
32037
  exports2.parseFollowUps = exports2.FollowUpsParseError = exports2.FollowUpManifestSchema = exports2.FollowUpItemSchema = exports2.SIGNAL_FILE = exports2.FOLLOW_UP_ARTIFACT = exports2.readEpicSignal = exports2.existingEpicIds = exports2.followUpIdFor = exports2.followUpEpicId = exports2.renderIntentMarkdown = exports2.openFollowUpEpic = exports2.openIncidentEpic = exports2.SignalParseError = exports2.isSignal = exports2.parseSignal = exports2.SignalSchema = exports2.EpicStepEditError = exports2.resolveStepRef = exports2.describeGateEffect = exports2.commitEpicStepEdit = exports2.planSetEpicStepGates = exports2.planRemoveEpicStep = exports2.planAddEpicStep = exports2.EpicScaffoldError = exports2.epicsRoot = exports2.mapStepStatusToEpic = exports2.mirrorRunStateToEpic = exports2.scaffoldEpic = exports2.resolveArtifactCommitConfig = exports2.commitApprovedArtifacts = exports2.PipelineAdaptError = exports2.applyAdaptation = exports2.parseAdaptationVerdict = exports2.buildAdaptationPrompt = exports2.buildPhaseCatalog = exports2.slugEpicId = exports2.parseClassificationVerdict = exports2.buildClassificationPrompt = exports2.heuristicClassify = exports2.PipelineAssembleError = exports2.recipePipelineId = exports2.assemblePipeline = exports2.collectWorkspaceRefIssues = exports2.stepDagId = exports2.stepAgentId = exports2.normalizeStep = exports2.WorkspaceValidationError = exports2.validateWorkspace = exports2.WorkspaceSchema = void 0;
31975
32038
  exports2.composeAgentPrompt = exports2.PROJECT_INSTRUCTION_FILES = exports2.findProjectInstructions = exports2.stripPersonaMetadata = exports2.PersonaLoader = exports2.SkillNotFoundError = exports2.SkillLoader = exports2.EnvVarMissingError = exports2.EnvResolver = exports2.epicPipelineReport = exports2.planEpicPipelineExtraction = exports2.epicOwningPipeline = exports2.unstageEpicPipeline = exports2.stageEpicPipeline = exports2.writeEpicPipelines = exports2.splitEpicPipelines = exports2.mergeEpicPipelines = exports2.epicPipelinePath = exports2.EPIC_PIPELINE_FILENAME = exports2.WORKSPACE_DIR = exports2.WORKSPACE_FILENAME = exports2.WorkspaceParseError = exports2.WorkspaceNotFoundError = exports2.WorkspaceLoader = exports2.renderPlanMarkdown = exports2.generatePlan = exports2.collectContext = exports2.followUpHookFailures = exports2.pendingFollowUpDone = exports2.applyFollowUpHookRun = exports2.trackFollowUpChildren = exports2.writeFollowUpHookLedger = exports2.readFollowUpHookLedger = exports2.followUpHookLedgerPath = exports2.runFollowUpHook = exports2.fillFollowUpHookCommand = exports2.resolveFollowUpHooks = exports2.epicPipelineId = exports2.FOLLOW_UP_HOOK_TIMEOUT_MS = exports2.FOLLOW_UP_HOOKS_LEDGER = exports2.stepProducesFollowUps = exports2.collectFollowUpHookIssues = exports2.FOLLOW_UP_HOOK_PLACEHOLDERS = exports2.FOLLOW_UP_HOOK_KEYS = exports2.FOLLOW_UP_INTENT = exports2.FOLLOW_UPS_FILE = exports2.openManifestFollowUp = exports2.followUpChildId = exports2.followUpsOf = exports2.readEpicFollowUps = void 0;
31976
- exports2.FileRunStateStore = exports2.RunStateStore = exports2.harnessCapabilities = exports2.NO_HARNESS_CAPABILITIES = exports2.RunnerValidationError = exports2.validateRunnerExport = exports2.CustomRunnerLoader = exports2.buildClaudeSpawnEnv = exports2.hasClaudeLogin = exports2.isInsideClaudeCodeSession = exports2.createJsonSink = exports2.createLineSink = exports2.isCodexMcpConfigured = exports2.codexConfigPath = exports2.readProjectMcpServer = exports2.mcpRegistrarFor = exports2.codexMcpRegistrar = exports2.claudeMcpRegistrar = exports2.CodexRunner = exports2.DefaultRunner = exports2.RunnerRegistry = exports2.targetPath = exports2.scopePaths = exports2.discoverAssets = exports2.commandBodyIsStale = exports2.strictModeSection = exports2.resolveEpicStrictMode = exports2.epicStrictMode = exports2.commandBodyPredatesStrictMode = exports2.STRICT_MODE_KEY = exports2.STRICT_MODE_HEADING = exports2.writeUserEpicIdPrefix = exports2.userConfigPath = exports2.readUserConfig = exports2.readGitUserName = exports2.ensureUserConfigIgnored = exports2.USER_CONFIG_RELPATH = exports2.USER_CONFIG_IGNORE_LINE = exports2.suggestEpicId = exports2.resolveEpicIdPrefixChain = exports2.resolveEpicIdPrefix = exports2.epicIdDateStamp = exports2.deriveEpicIdPrefix = exports2.EPIC_ID_PREFIX_PATTERN = exports2.EPIC_ID_PREFIX_KEY = exports2.resolveArtifactLanguage = exports2.commandBodyPredatesArtifactLanguage = exports2.artifactLanguageSection = exports2.ARTIFACT_LANGUAGE_HEADING = exports2.stripPersonaDirectives = void 0;
31977
- exports2.getBuiltinArtifactTemplates = exports2.phaseArtifactFileName = exports2.sdlcClaudeCommand = exports2.builtinClaudeCommand = exports2.loadAllBuiltinPresets = exports2.loadBuiltinPreset = exports2.planRecipeMigration = exports2.getBuiltinRecipeSummaries = exports2.getAllBuiltinPipelineSummaries = exports2.getSdlcBuiltinPipelineSummary = exports2.getBuiltinPipelineSummary = exports2.getBuiltinWorkflowByPipelineId = exports2.getBuiltinWorkflow = exports2.workflowSlug = exports2.builtinTemplatesRoot = exports2.pipelineCommandId = exports2.PHASES = exports2.BUILTIN_WORKFLOWS = exports2.withBackfilledStepNames = exports2.describeDrift = exports2.reconcileRunSteps = exports2.RUN_STATE_SCHEMA_VERSION = exports2.migrateRunState = exports2.stepIdentity = exports2.resolvePath = exports2.AutoReviewerError = exports2.runAutoReview = exports2.renderRunReport = exports2.verifyRun = exports2.runExecLoop = exports2.estimateCostUsd = exports2.providerAliases = exports2.ratesFromConfig = exports2.mergeRates = exports2.BUILTIN_RATES = exports2.checkBudget = exports2.PipelineRunError = exports2.submitAutoReviewVerdict = exports2.requestStepUpdate = exports2.rerunStep = exports2.rejectStep = exports2.approveStep = exports2.markStepDone = exports2.canStartStep = exports2.startRun = exports2.activateBackendFromWorkspace = exports2.activateRunStateBackend = exports2.resolveRunStateBackend = exports2.GitRunStateStore = exports2.RUN_ID_PATTERN = void 0;
31978
- exports2.loadActiveProfile = exports2.loadProfile = exports2.listProfileManifests = exports2.resolveStandard = exports2.workspaceStandard = exports2.isBuiltinProfileId = exports2.ProfileSchema = exports2.TRACE_RULES = exports2.PROFILES_DIR = exports2.DEFAULT_PROFILE_ID = exports2.BUILTIN_PROFILE_IDS = exports2.setEpicMemoryHook = exports2.isEpicMemoryHookEnabled = exports2.installAnnotationTools = exports2.relativeEpicRoot = exports2.provisionDeclaredWorkflows = exports2.provisionWorkflowFiles = exports2.writeWorkflowArtifactTemplates = exports2.writeWorkflowCommands = exports2.provisionShortcutDocs = exports2.writeTwoLayerCommands = exports2.shortcutCommandDoc = exports2.backboneCommandDoc = exports2.unprovisionedPhases = exports2.nextEligiblePhase = exports2.resolveComposition = exports2.shortcutCommandId = exports2.getCanonicalPhase = exports2.isCanonicalPhase = exports2.BACKBONE_COMMAND_ID = exports2.CANONICAL_PHASE_IDS = exports2.CANONICAL_PHASES = exports2.renderTemplate = exports2.DEFAULT_GLOBAL_WORKFLOW_IDS = exports2.detectGlobalBuiltinSource = exports2.uninstallWorkflowGlobalsByIds = exports2.isWorkflowGloballyInstalled = exports2.installWorkflowGlobalsByIds = exports2.installGlobalDefaults = exports2.resolveProviderModel = exports2.isClaudeTierAlias = exports2.CLAUDE_TIER_ALIASES = exports2.FAST_MODEL = exports2.CODING_MODEL = exports2.PLANNING_MODEL = exports2.isBuiltinPreset = exports2.BUILTIN_PRESET_IDS = exports2.writeBuiltinAutoReviewValidators = exports2.resolvePrimaryStack = exports2.getSdlcArtifactTemplates = void 0;
31979
- exports2.AIDLC_CORE_VERSION = exports2.AIDLC_CLI_GUIDE_TEXT = exports2.AIDLC_KNOWLEDGE = exports2.clearResolveCommandCache = exports2.resolveCommand = exports2.remapClaudePath = exports2.claudeConfigEnv = exports2.claudeJsonPath = exports2.isDefaultClaudeConfigDir = exports2.claudeConfigDir = exports2.resolveClaudeConfigDir = exports2.defaultClaudeConfigDir = exports2.getClaudeConfigDirOverride = exports2.setClaudeConfigDir = exports2.resolveDeclaredPath = exports2.expandHome = exports2.ProfileLoadError = exports2.UnknownStandardError = exports2.builtinProfiles = void 0;
32039
+ exports2.buildClaudeSpawnEnv = exports2.hasClaudeLogin = exports2.isInsideClaudeCodeSession = exports2.createJsonSink = exports2.createLineSink = exports2.isCodexMcpConfigured = exports2.codexConfigPath = exports2.readProjectMcpServer = exports2.mcpRegistrarFor = exports2.codexMcpRegistrar = exports2.claudeMcpRegistrar = exports2.CodexRunner = exports2.DefaultRunner = exports2.RunnerRegistry = exports2.targetPath = exports2.scopePaths = exports2.discoverAssets = exports2.commandBodyIsStale = exports2.readEpicTags = exports2.normalizeTags = exports2.normalizeTag = exports2.epicMatchesTags = exports2.applyTagEdit = exports2.MAX_TAG_LENGTH = exports2.EPIC_TAGS_KEY = exports2.strictModeSection = exports2.resolveEpicStrictMode = exports2.epicStrictMode = exports2.commandBodyPredatesStrictMode = exports2.STRICT_MODE_KEY = exports2.STRICT_MODE_HEADING = exports2.writeUserEpicIdPrefix = exports2.userConfigPath = exports2.readUserConfig = exports2.readGitUserName = exports2.ensureUserConfigIgnored = exports2.USER_CONFIG_RELPATH = exports2.USER_CONFIG_IGNORE_LINE = exports2.suggestEpicId = exports2.resolveEpicIdPrefixChain = exports2.resolveEpicIdPrefix = exports2.epicIdDateStamp = exports2.deriveEpicIdPrefix = exports2.EPIC_ID_PREFIX_PATTERN = exports2.EPIC_ID_PREFIX_KEY = exports2.resolveArtifactLanguage = exports2.commandBodyPredatesArtifactLanguage = exports2.artifactLanguageSection = exports2.ARTIFACT_LANGUAGE_HEADING = exports2.stripPersonaDirectives = void 0;
32040
+ exports2.getBuiltinRecipeSummaries = exports2.getAllBuiltinPipelineSummaries = exports2.getSdlcBuiltinPipelineSummary = exports2.getBuiltinPipelineSummary = exports2.getBuiltinWorkflowByPipelineId = exports2.getBuiltinWorkflow = exports2.workflowSlug = exports2.builtinTemplatesRoot = exports2.pipelineCommandId = exports2.PHASES = exports2.BUILTIN_WORKFLOWS = exports2.withBackfilledStepNames = exports2.describeDrift = exports2.reconcileRunSteps = exports2.RUN_STATE_SCHEMA_VERSION = exports2.migrateRunState = exports2.stepIdentity = exports2.resolvePath = exports2.AutoReviewerError = exports2.runAutoReview = exports2.renderRunReport = exports2.verifyRun = exports2.runExecLoop = exports2.estimateCostUsd = exports2.providerAliases = exports2.ratesFromConfig = exports2.mergeRates = exports2.BUILTIN_RATES = exports2.checkBudget = exports2.PipelineRunError = exports2.submitAutoReviewVerdict = exports2.requestStepUpdate = exports2.rerunStep = exports2.rejectStep = exports2.approveStep = exports2.markStepDone = exports2.canStartStep = exports2.startRun = exports2.activateBackendFromWorkspace = exports2.activateRunStateBackend = exports2.resolveRunStateBackend = exports2.GitRunStateStore = exports2.RUN_ID_PATTERN = exports2.FileRunStateStore = exports2.RunStateStore = exports2.harnessCapabilities = exports2.NO_HARNESS_CAPABILITIES = exports2.RunnerValidationError = exports2.validateRunnerExport = exports2.CustomRunnerLoader = void 0;
32041
+ exports2.TRACE_RULES = exports2.PROFILES_DIR = exports2.DEFAULT_PROFILE_ID = exports2.BUILTIN_PROFILE_IDS = exports2.setEpicMemoryHook = exports2.isEpicMemoryHookEnabled = exports2.installAnnotationTools = exports2.relativeEpicRoot = exports2.provisionDeclaredWorkflows = exports2.provisionWorkflowFiles = exports2.writeWorkflowArtifactTemplates = exports2.writeWorkflowCommands = exports2.provisionShortcutDocs = exports2.writeTwoLayerCommands = exports2.shortcutCommandDoc = exports2.backboneCommandDoc = exports2.unprovisionedPhases = exports2.nextEligiblePhase = exports2.resolveComposition = exports2.shortcutCommandId = exports2.getCanonicalPhase = exports2.isCanonicalPhase = exports2.BACKBONE_COMMAND_ID = exports2.CANONICAL_PHASE_IDS = exports2.CANONICAL_PHASES = exports2.renderTemplate = exports2.DEFAULT_GLOBAL_WORKFLOW_IDS = exports2.detectGlobalBuiltinSource = exports2.uninstallWorkflowGlobalsByIds = exports2.isWorkflowGloballyInstalled = exports2.installWorkflowGlobalsByIds = exports2.installGlobalDefaults = exports2.resolveProviderModel = exports2.isClaudeTierAlias = exports2.CLAUDE_TIER_ALIASES = exports2.FAST_MODEL = exports2.CODING_MODEL = exports2.PLANNING_MODEL = exports2.isBuiltinPreset = exports2.BUILTIN_PRESET_IDS = exports2.writeBuiltinAutoReviewValidators = exports2.resolvePrimaryStack = exports2.getSdlcArtifactTemplates = exports2.getBuiltinArtifactTemplates = exports2.phaseArtifactFileName = exports2.sdlcClaudeCommand = exports2.builtinClaudeCommand = exports2.loadAllBuiltinPresets = exports2.loadBuiltinPreset = exports2.planRecipeMigration = void 0;
32042
+ exports2.AIDLC_CORE_VERSION = exports2.AIDLC_CLI_GUIDE_TEXT = exports2.AIDLC_KNOWLEDGE = exports2.clearResolveCommandCache = exports2.resolveCommand = exports2.remapClaudePath = exports2.claudeConfigEnv = exports2.claudeJsonPath = exports2.isDefaultClaudeConfigDir = exports2.claudeConfigDir = exports2.resolveClaudeConfigDir = exports2.defaultClaudeConfigDir = exports2.getClaudeConfigDirOverride = exports2.setClaudeConfigDir = exports2.resolveDeclaredPath = exports2.expandHome = exports2.ProfileLoadError = exports2.UnknownStandardError = exports2.builtinProfiles = exports2.loadActiveProfile = exports2.loadProfile = exports2.listProfileManifests = exports2.resolveStandard = exports2.workspaceStandard = exports2.isBuiltinProfileId = exports2.ProfileSchema = void 0;
31980
32043
  var WorkspaceSchema_1 = require_WorkspaceSchema();
31981
32044
  Object.defineProperty(exports2, "WorkspaceSchema", { enumerable: true, get: function() {
31982
32045
  return WorkspaceSchema_1.WorkspaceSchema;
@@ -32377,6 +32440,28 @@ var require_dist = __commonJS({
32377
32440
  Object.defineProperty(exports2, "strictModeSection", { enumerable: true, get: function() {
32378
32441
  return strictMode_1.strictModeSection;
32379
32442
  } });
32443
+ var epicTags_1 = require_epicTags();
32444
+ Object.defineProperty(exports2, "EPIC_TAGS_KEY", { enumerable: true, get: function() {
32445
+ return epicTags_1.EPIC_TAGS_KEY;
32446
+ } });
32447
+ Object.defineProperty(exports2, "MAX_TAG_LENGTH", { enumerable: true, get: function() {
32448
+ return epicTags_1.MAX_TAG_LENGTH;
32449
+ } });
32450
+ Object.defineProperty(exports2, "applyTagEdit", { enumerable: true, get: function() {
32451
+ return epicTags_1.applyTagEdit;
32452
+ } });
32453
+ Object.defineProperty(exports2, "epicMatchesTags", { enumerable: true, get: function() {
32454
+ return epicTags_1.epicMatchesTags;
32455
+ } });
32456
+ Object.defineProperty(exports2, "normalizeTag", { enumerable: true, get: function() {
32457
+ return epicTags_1.normalizeTag;
32458
+ } });
32459
+ Object.defineProperty(exports2, "normalizeTags", { enumerable: true, get: function() {
32460
+ return epicTags_1.normalizeTags;
32461
+ } });
32462
+ Object.defineProperty(exports2, "readEpicTags", { enumerable: true, get: function() {
32463
+ return epicTags_1.readEpicTags;
32464
+ } });
32380
32465
  var commandBodyFreshness_1 = require_commandBodyFreshness();
32381
32466
  Object.defineProperty(exports2, "commandBodyIsStale", { enumerable: true, get: function() {
32382
32467
  return commandBodyFreshness_1.commandBodyIsStale;
@@ -41510,7 +41595,7 @@ var require_package = __commonJS({
41510
41595
  "package.json"(exports2, module2) {
41511
41596
  module2.exports = {
41512
41597
  name: "@delete101020/aidlc",
41513
- version: "4.0.0",
41598
+ version: "4.0.2",
41514
41599
  description: "AIDLC Native CLI, a fork of AIDLC \u2014 drive Claude through the six-stage AI-Native SDLC Playbook: Plan \u2192 Design \u2192 Build \u2192 Test \u2192 Deploy \u2192 Maintain, with artifact gates, requirements traceability, an incident loop back to Plan, and Spec Kit support.",
41515
41600
  license: "MIT",
41516
41601
  repository: {
@@ -44179,7 +44264,7 @@ var safeLoadAll = renamed("safeLoadAll", "loadAll");
44179
44264
  var safeDump = renamed("safeDump", "dump");
44180
44265
 
44181
44266
  // src/index.ts
44182
- var import_core25 = __toESM(require_dist());
44267
+ var import_core26 = __toESM(require_dist());
44183
44268
 
44184
44269
  // src/commands/validate.ts
44185
44270
  var import_chalk = __toESM(require_source());
@@ -47130,11 +47215,12 @@ var http = __toESM(require("http"));
47130
47215
  var path11 = __toESM(require("path"));
47131
47216
  var import_chalk14 = __toESM(require_source());
47132
47217
  var import_chokidar3 = __toESM(require_chokidar());
47133
- var import_core17 = __toESM(require_dist());
47218
+ var import_core18 = __toESM(require_dist());
47134
47219
 
47135
47220
  // src/epicsList.ts
47136
47221
  var fs9 = __toESM(require("fs"));
47137
47222
  var path10 = __toESM(require("path"));
47223
+ var import_core17 = __toESM(require_dist());
47138
47224
  var STATUS_VALUES = ["pending", "in_progress", "done", "failed"];
47139
47225
  function asStatus(v) {
47140
47226
  return STATUS_VALUES.includes(v) ? v : "pending";
@@ -47178,6 +47264,7 @@ function listEpics(workspaceRoot, doc) {
47178
47264
  description: typeof parsed.description === "string" ? parsed.description : "",
47179
47265
  status: asStatus(parsed.status),
47180
47266
  createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : "",
47267
+ tags: (0, import_core17.readEpicTags)(parsed),
47181
47268
  pipeline: typeof parsed.pipeline === "string" ? parsed.pipeline : null,
47182
47269
  agents: Array.isArray(parsed.agents) ? parsed.agents.map(String) : [],
47183
47270
  currentStep: typeof parsed.currentStep === "number" ? parsed.currentStep : 0,
@@ -47234,12 +47321,12 @@ function registerDashboard(program3) {
47234
47321
  }
47235
47322
  if (url.pathname === "/api/runs") {
47236
47323
  res.writeHead(200, { "Content-Type": "application/json" });
47237
- res.end(JSON.stringify(import_core17.RunStateStore.list(root)));
47324
+ res.end(JSON.stringify(import_core18.RunStateStore.list(root)));
47238
47325
  return;
47239
47326
  }
47240
47327
  if (url.pathname.startsWith("/api/runs/")) {
47241
47328
  const runId = url.pathname.slice("/api/runs/".length);
47242
- const state = import_core17.RunStateStore.load(root, runId);
47329
+ const state = import_core18.RunStateStore.load(root, runId);
47243
47330
  if (!state) {
47244
47331
  res.writeHead(404);
47245
47332
  res.end("not found");
@@ -47381,7 +47468,7 @@ function handleAction(req, res, root) {
47381
47468
  res.end(JSON.stringify({ error: "invalid JSON" }));
47382
47469
  return;
47383
47470
  }
47384
- const state = import_core17.RunStateStore.load(root, payload.runId);
47471
+ const state = import_core18.RunStateStore.load(root, payload.runId);
47385
47472
  if (!state) {
47386
47473
  res.writeHead(404);
47387
47474
  res.end(JSON.stringify({ error: `run not found: ${payload.runId}` }));
@@ -47391,31 +47478,31 @@ function handleAction(req, res, root) {
47391
47478
  try {
47392
47479
  switch (payload.type) {
47393
47480
  case "mark-done": {
47394
- const ws = import_core17.WorkspaceLoader.load(root);
47481
+ const ws = import_core18.WorkspaceLoader.load(root);
47395
47482
  const pipeline = ws.config.pipelines.find((p) => p.id === state.pipelineId);
47396
47483
  if (!pipeline) {
47397
47484
  throw new Error(`pipeline ${state.pipelineId} not found`);
47398
47485
  }
47399
- next = (0, import_core17.markStepDone)({ state, pipeline, workspaceRoot: root });
47486
+ next = (0, import_core18.markStepDone)({ state, pipeline, workspaceRoot: root });
47400
47487
  break;
47401
47488
  }
47402
47489
  case "approve": {
47403
- const ws = import_core17.WorkspaceLoader.load(root);
47490
+ const ws = import_core18.WorkspaceLoader.load(root);
47404
47491
  const pipeline = ws.config.pipelines.find((p) => p.id === state.pipelineId);
47405
47492
  if (!pipeline) {
47406
47493
  throw new Error(`pipeline ${state.pipelineId} not found`);
47407
47494
  }
47408
- next = (0, import_core17.approveStep)({ state, pipeline });
47495
+ next = (0, import_core18.approveStep)({ state, pipeline });
47409
47496
  if (payload.comment) {
47410
47497
  next.steps[state.currentStepIdx].feedback = payload.comment;
47411
47498
  }
47412
47499
  break;
47413
47500
  }
47414
47501
  case "reject":
47415
- next = (0, import_core17.rejectStep)({ state, reason: payload.reason ?? "" });
47502
+ next = (0, import_core18.rejectStep)({ state, reason: payload.reason ?? "" });
47416
47503
  break;
47417
47504
  case "rerun":
47418
- next = (0, import_core17.rerunStep)({ state, feedback: payload.feedback });
47505
+ next = (0, import_core18.rerunStep)({ state, feedback: payload.feedback });
47419
47506
  break;
47420
47507
  default:
47421
47508
  res.writeHead(400);
@@ -47424,7 +47511,7 @@ function handleAction(req, res, root) {
47424
47511
  }
47425
47512
  } catch (err) {
47426
47513
  const msg2 = err instanceof Error ? err.message : String(err);
47427
- const missing = err instanceof import_core17.PipelineRunError ? err.missing : void 0;
47514
+ const missing = err instanceof import_core18.PipelineRunError ? err.missing : void 0;
47428
47515
  res.writeHead(400);
47429
47516
  res.end(JSON.stringify({ error: msg2, missing }));
47430
47517
  return;
@@ -47963,27 +48050,39 @@ var import_chalk15 = __toESM(require_source());
47963
48050
  var import_cli_table32 = __toESM(require_cli_table3());
47964
48051
  var fs10 = __toESM(require("fs"));
47965
48052
  var path12 = __toESM(require("path"));
47966
- var import_core18 = __toESM(require_dist());
48053
+ var import_core19 = __toESM(require_dist());
47967
48054
  function registerEpic(program3) {
47968
48055
  const cmd = program3.command("epic").description("List + inspect epics from <state.root>/<id>/state.json (mirrors the extension)");
47969
- cmd.command("list").description("List all epics found under workspace state.root (default: docs/epics/)").option("--json", "Output raw JSON").option("--status <status>", "Filter by status (pending | in_progress | done | failed)").action((opts, actionCmd) => {
48056
+ cmd.command("list").description("List all epics found under workspace state.root (default: docs/epics/)").option("--json", "Output raw JSON").option("--status <status>", "Filter by status (pending | in_progress | done | failed)").option(
48057
+ "--tag <tag>",
48058
+ 'Filter by tag \u2014 repeatable, and an epic must carry all of them. Case and accents are folded, so --tag "thanh to\xE1n" finds THANH-TOAN',
48059
+ collectKv,
48060
+ []
48061
+ ).action((opts, actionCmd) => {
47970
48062
  const root = resolveWorkspaceRoot(actionCmd);
47971
48063
  const doc = readYaml(root);
47972
48064
  let epics = listEpics(root, doc);
47973
48065
  if (opts.status) {
47974
48066
  epics = epics.filter((e) => e.status === opts.status);
47975
48067
  }
48068
+ const wantedTags = (0, import_core19.normalizeTags)(opts.tag);
48069
+ if (wantedTags.length > 0) {
48070
+ epics = epics.filter((e) => (0, import_core19.epicMatchesTags)(e.tags, wantedTags));
48071
+ }
47976
48072
  if (opts.json) {
47977
48073
  console.log(JSON.stringify(epics, null, 2));
47978
48074
  return;
47979
48075
  }
47980
48076
  if (epics.length === 0) {
47981
48077
  console.log(import_chalk15.default.dim("No epics found."));
48078
+ if (wantedTags.length > 0) {
48079
+ console.log(import_chalk15.default.dim(` tags = ${wantedTags.join(" + ")}`));
48080
+ }
47982
48081
  console.log(import_chalk15.default.dim(` state.root = ${doc?.state ? doc.state.root ?? "docs/epics" : "docs/epics"}`));
47983
48082
  return;
47984
48083
  }
47985
48084
  const table = new import_cli_table32.default({
47986
- head: [import_chalk15.default.bold("Epic"), import_chalk15.default.bold("Title"), import_chalk15.default.bold("Progress"), import_chalk15.default.bold("Status"), import_chalk15.default.bold("Pipeline")],
48085
+ head: [import_chalk15.default.bold("Epic"), import_chalk15.default.bold("Title"), import_chalk15.default.bold("Tags"), import_chalk15.default.bold("Progress"), import_chalk15.default.bold("Status"), import_chalk15.default.bold("Pipeline")],
47987
48086
  style: { head: [], border: [] }
47988
48087
  });
47989
48088
  for (const epic of epics) {
@@ -47994,6 +48093,7 @@ function registerEpic(program3) {
47994
48093
  table.push([
47995
48094
  import_chalk15.default.bold(epic.id),
47996
48095
  truncate(epic.title || import_chalk15.default.dim("(untitled)"), 40),
48096
+ epic.tags.length > 0 ? import_chalk15.default.cyan(truncate(epic.tags.join(" "), 28)) : import_chalk15.default.dim("\u2014"),
47997
48097
  stepLabel2,
47998
48098
  colorEpicStatus(epic.status),
47999
48099
  import_chalk15.default.dim(epic.pipeline ?? "\u2014")
@@ -48021,7 +48121,7 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48021
48121
  }
48022
48122
  printEpicDetail(epic);
48023
48123
  });
48024
- cmd.command("start <epicId>").description(`Scaffold a new epic on disk (folder + artifacts + run state) \u2014 mirrors the extension's "Start epic"`).option("--recipe <id>", "assemble a right-sized pipeline from this recipe").option("--pipeline <id>", "use an existing pipeline as-is").option("--brief <text...>", "classify this requirement brief into a recipe, then assemble").option("--llm", "use the `claude` CLI to classify --brief (falls back to heuristic)").option("--from <pipelineId>", "override the recipe's source pipeline").option("--title <title>", "epic title").option("--desc <description>", "epic description / requirement snapshot").option("--input <kv>", "capability input as key=value (repeatable)", collectKv, []).option("--no-strict", "work to the size of this epic \u2014 no invented NFR / risk / alternatives sections").action((epicId, opts, actionCmd) => {
48124
+ cmd.command("start <epicId>").description(`Scaffold a new epic on disk (folder + artifacts + run state) \u2014 mirrors the extension's "Start epic"`).option("--recipe <id>", "assemble a right-sized pipeline from this recipe").option("--pipeline <id>", "use an existing pipeline as-is").option("--brief <text...>", "classify this requirement brief into a recipe, then assemble").option("--llm", "use the `claude` CLI to classify --brief (falls back to heuristic)").option("--from <pipelineId>", "override the recipe's source pipeline").option("--title <title>", "epic title").option("--desc <description>", "epic description / requirement snapshot").option("--input <kv>", "capability input as key=value (repeatable)", collectKv, []).option("--tag <tag>", 'tag for this epic (repeatable) \u2014 stored uppercase, e.g. "thanh to\xE1n" \u2192 THANH-TOAN', collectKv, []).option("--no-strict", "work to the size of this epic \u2014 no invented NFR / risk / alternatives sections").action((epicId, opts, actionCmd) => {
48025
48125
  const root = resolveWorkspaceRoot(actionCmd);
48026
48126
  const doc = requireYaml(root);
48027
48127
  const modes = [opts.recipe, opts.pipeline, opts.brief?.length ? "brief" : void 0].filter(Boolean).length;
@@ -48031,7 +48131,7 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48031
48131
  }
48032
48132
  let config;
48033
48133
  try {
48034
- config = (0, import_core18.validateWorkspace)(doc, ".aidlc/workspace.yaml");
48134
+ config = (0, import_core19.validateWorkspace)(doc, ".aidlc/workspace.yaml");
48035
48135
  } catch (err) {
48036
48136
  console.error(import_chalk15.default.red("workspace.yaml is invalid \u2014 fix it before starting an epic:"));
48037
48137
  console.error(import_chalk15.default.dim(err instanceof Error ? err.message : String(err)));
@@ -48055,7 +48155,7 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48055
48155
  let recipeId = opts.recipe;
48056
48156
  if (!recipeId) {
48057
48157
  const brief = (opts.brief ?? []).join(" ").trim();
48058
- const heur = (0, import_core18.heuristicClassify)(brief, config.recipes);
48158
+ const heur = (0, import_core19.heuristicClassify)(brief, config.recipes);
48059
48159
  let verdict = heur;
48060
48160
  if (opts.llm) {
48061
48161
  console.log(import_chalk15.default.dim(`Provisional \u2192 ${heur.recipeId} (${heur.confidence}, heuristic) \u2014 refining with claude\u2026`));
@@ -48070,20 +48170,20 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48070
48170
  recipe.from = opts.from;
48071
48171
  }
48072
48172
  }
48073
- const pipelineId = (0, import_core18.recipePipelineId)({ recipeId, epicId, taken: existingIds(doc.pipelines) });
48173
+ const pipelineId = (0, import_core19.recipePipelineId)({ recipeId, epicId, taken: existingIds(doc.pipelines) });
48074
48174
  try {
48075
- pipelineCfg = (0, import_core18.assemblePipeline)(config, { recipeId, pipelineId });
48175
+ pipelineCfg = (0, import_core19.assemblePipeline)(config, { recipeId, pipelineId });
48076
48176
  } catch (err) {
48077
- if (err instanceof import_core18.PipelineAssembleError) {
48177
+ if (err instanceof import_core19.PipelineAssembleError) {
48078
48178
  console.error(import_chalk15.default.red("Could not assemble pipeline: ") + import_chalk15.default.dim(err.message));
48079
48179
  process.exit(1);
48080
48180
  }
48081
48181
  throw err;
48082
48182
  }
48083
48183
  doc.pipelines.push(pipelineCfg);
48084
- (0, import_core18.stageEpicPipeline)(doc, pipelineCfg.id, epicId);
48184
+ (0, import_core19.stageEpicPipeline)(doc, pipelineCfg.id, epicId);
48085
48185
  try {
48086
- (0, import_core18.validateWorkspace)(doc, ".aidlc/workspace.yaml");
48186
+ (0, import_core19.validateWorkspace)(doc, ".aidlc/workspace.yaml");
48087
48187
  } catch (err) {
48088
48188
  console.error(import_chalk15.default.red("Assembled pipeline failed validation \u2014 not written:"));
48089
48189
  console.error(import_chalk15.default.dim(err instanceof Error ? err.message : String(err)));
@@ -48092,7 +48192,7 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48092
48192
  writeYaml(root, doc);
48093
48193
  console.log(import_chalk15.default.dim(`Assembled pipeline ${import_chalk15.default.bold(pipelineCfg.id)} from recipe ${import_chalk15.default.bold(recipeId)}`));
48094
48194
  }
48095
- const agents = Array.isArray(pipelineCfg.steps) ? pipelineCfg.steps.map(import_core18.stepAgentId) : [];
48195
+ const agents = Array.isArray(pipelineCfg.steps) ? pipelineCfg.steps.map(import_core19.stepAgentId) : [];
48096
48196
  const inputs = {};
48097
48197
  for (const kv of opts.input) {
48098
48198
  const eq = kv.indexOf("=");
@@ -48101,7 +48201,7 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48101
48201
  }
48102
48202
  }
48103
48203
  try {
48104
- const { epicDir } = (0, import_core18.scaffoldEpic)({
48204
+ const { epicDir } = (0, import_core19.scaffoldEpic)({
48105
48205
  workspaceRoot: root,
48106
48206
  doc,
48107
48207
  epicId,
@@ -48111,13 +48211,18 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48111
48211
  agents,
48112
48212
  inputs,
48113
48213
  pipeline: pipelineCfg,
48114
- strictMode: opts.strict
48214
+ strictMode: opts.strict,
48215
+ tags: opts.tag
48115
48216
  });
48217
+ const tags = (0, import_core19.normalizeTags)(opts.tag);
48116
48218
  const steps = agents.join(" \u2192 ");
48117
48219
  console.log(import_chalk15.default.green("\u2714") + ` Started epic ${import_chalk15.default.bold(epicId)}`);
48118
48220
  console.log(import_chalk15.default.dim(` Pipeline: ${pipelineCfg.id}`));
48119
48221
  console.log(import_chalk15.default.dim(` Steps: ${steps}`));
48120
48222
  console.log(import_chalk15.default.dim(` Dir: ${epicDir}`));
48223
+ if (tags.length > 0) {
48224
+ console.log(import_chalk15.default.dim(" Tags: ") + import_chalk15.default.cyan(tags.join(" ")));
48225
+ }
48121
48226
  if (!opts.strict) {
48122
48227
  console.log(import_chalk15.default.dim(" Depth: strict_mode: false \u2014 phases stay proportional to the work"));
48123
48228
  }
@@ -48131,20 +48236,79 @@ ${epics.length} epic${epics.length !== 1 ? "s" : ""}`));
48131
48236
  console.log(`
48132
48237
  Run ${import_chalk15.default.cyan(`${runCmd} ${epicId}`)} in Claude to begin.`);
48133
48238
  } catch (err) {
48134
- if (err instanceof import_core18.EpicScaffoldError) {
48239
+ if (err instanceof import_core19.EpicScaffoldError) {
48135
48240
  console.error(import_chalk15.default.red(err.message));
48136
48241
  process.exit(1);
48137
48242
  }
48138
48243
  throw err;
48139
48244
  }
48140
48245
  });
48246
+ cmd.command("tag <id> [tags...]").description("Show or edit an epic's tags. Bare `tag <id>` prints them; extra words are added.").option("--add <tag>", "add a tag (repeatable) \u2014 same as passing it positionally", collectKv, []).option("--remove <tag>", "remove a tag (repeatable)", collectKv, []).option("--set <tag>", "replace every tag with these (repeatable)", collectKv, []).option("--clear", "remove every tag").option("--json", "Output the resulting tags as JSON").action((id, positional, opts, actionCmd) => {
48247
+ const root = resolveWorkspaceRoot(actionCmd);
48248
+ const doc = readYaml(root);
48249
+ const file = path12.join((0, import_core19.epicsRoot)(root, doc), id, "state.json");
48250
+ if (!fs10.existsSync(file)) {
48251
+ const all = listEpics(root, doc).map((e) => e.id);
48252
+ console.error(import_chalk15.default.red(`Epic "${id}" not found.`));
48253
+ if (all.length > 0) {
48254
+ console.error(import_chalk15.default.dim(`Available: ${all.join(", ")}`));
48255
+ }
48256
+ process.exit(1);
48257
+ }
48258
+ let state;
48259
+ try {
48260
+ state = JSON.parse(fs10.readFileSync(file, "utf8"));
48261
+ } catch (err) {
48262
+ console.error(import_chalk15.default.red(`Could not read ${file}: ${err instanceof Error ? err.message : String(err)}`));
48263
+ process.exit(1);
48264
+ return;
48265
+ }
48266
+ const before = (0, import_core19.readEpicTags)(state);
48267
+ const add = [...positional, ...opts.add];
48268
+ const clear = opts.clear === true;
48269
+ const hasEdit = clear || add.length > 0 || opts.remove.length > 0 || opts.set.length > 0;
48270
+ if (!hasEdit) {
48271
+ if (opts.json) {
48272
+ console.log(JSON.stringify(before, null, 2));
48273
+ return;
48274
+ }
48275
+ console.log(before.length > 0 ? import_chalk15.default.cyan(before.join(" ")) : import_chalk15.default.dim("(no tags)"));
48276
+ return;
48277
+ }
48278
+ const after = (0, import_core19.applyTagEdit)(before, {
48279
+ // `--clear` is `--set` with nothing in it; spelling it separately keeps
48280
+ // "remove everything" from needing an empty-string argument.
48281
+ set: clear ? [] : opts.set.length > 0 ? opts.set : void 0,
48282
+ add,
48283
+ remove: opts.remove
48284
+ });
48285
+ if (after.join("\0") === before.join("\0")) {
48286
+ console.log(import_chalk15.default.dim(`No change \u2014 ${id} already has ${before.length > 0 ? import_chalk15.default.cyan(before.join(" ")) : "no tags"}.`));
48287
+ return;
48288
+ }
48289
+ state[import_core19.EPIC_TAGS_KEY] = after;
48290
+ try {
48291
+ fs10.writeFileSync(file, JSON.stringify(state, null, 2) + "\n", "utf8");
48292
+ } catch (err) {
48293
+ console.error(import_chalk15.default.red(`Could not write ${file}: ${err instanceof Error ? err.message : String(err)}`));
48294
+ process.exit(1);
48295
+ }
48296
+ if (opts.json) {
48297
+ console.log(JSON.stringify(after, null, 2));
48298
+ return;
48299
+ }
48300
+ console.log(import_chalk15.default.green("\u2714") + ` ${import_chalk15.default.bold(id)} tags: ` + (after.length > 0 ? import_chalk15.default.cyan(after.join(" ")) : import_chalk15.default.dim("(none)")));
48301
+ if (before.length > 0) {
48302
+ console.log(import_chalk15.default.dim(` was: ${before.join(" ")}`));
48303
+ }
48304
+ });
48141
48305
  const pipelineCmd = cmd.command("pipeline").description("Move epic-owned pipelines out of the shared workspace.yaml");
48142
48306
  pipelineCmd.command("extract").description(
48143
48307
  "Move each epic's pipeline out of .aidlc/workspace.yaml into\n <state.root>/<epic>/pipeline.yaml. Only pipelines an epic names in its\n own state.json move; shared and hand-authored ones stay put."
48144
48308
  ).option("--dry-run", "List what would move, write nothing").action((opts, actionCmd) => {
48145
48309
  const root = resolveWorkspaceRoot(actionCmd);
48146
48310
  const doc = requireYaml(root);
48147
- const plan = (0, import_core18.planEpicPipelineExtraction)(root, doc);
48311
+ const plan = (0, import_core19.planEpicPipelineExtraction)(root, doc);
48148
48312
  if (plan.length === 0) {
48149
48313
  console.log(import_chalk15.default.dim("No inline epic pipelines left to move."));
48150
48314
  return;
@@ -48160,7 +48324,7 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48160
48324
  return;
48161
48325
  }
48162
48326
  for (const item of plan) {
48163
- (0, import_core18.stageEpicPipeline)(doc, item.pipelineId, item.epicId);
48327
+ (0, import_core19.stageEpicPipeline)(doc, item.pipelineId, item.epicId);
48164
48328
  }
48165
48329
  writeYaml(root, doc);
48166
48330
  console.log(import_chalk15.default.green("\u2714") + ` Moved ${plan.length} pipeline(s) out of .aidlc/workspace.yaml.`);
@@ -48169,19 +48333,19 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48169
48333
  cmd.command("next-id").description("Print the id to give the next epic \u2014 honours epic_id_prefix in .aidlc/user.yaml").action((_opts, actionCmd) => {
48170
48334
  const root = resolveWorkspaceRoot(actionCmd);
48171
48335
  const doc = requireYaml(root);
48172
- const dir = (0, import_core18.epicsRoot)(root, doc);
48336
+ const dir = (0, import_core19.epicsRoot)(root, doc);
48173
48337
  const existing = fs10.existsSync(dir) ? fs10.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
48174
- const { prefix } = (0, import_core18.resolveEpicIdPrefixChain)({
48175
- user: (0, import_core18.readUserConfig)(root),
48338
+ const { prefix } = (0, import_core19.resolveEpicIdPrefixChain)({
48339
+ user: (0, import_core19.readUserConfig)(root),
48176
48340
  workspace: doc,
48177
- gitUserName: (0, import_core18.readGitUserName)(root)
48341
+ gitUserName: (0, import_core19.readGitUserName)(root)
48178
48342
  });
48179
- console.log((0, import_core18.suggestEpicId)(existing, prefix));
48343
+ console.log((0, import_core19.suggestEpicId)(existing, prefix));
48180
48344
  });
48181
48345
  cmd.command("strict <epicId> [value]").description("Show or set an epic's depth of work (strict_mode in its state.json)").action((epicId, value, _opts, actionCmd) => {
48182
48346
  const root = resolveWorkspaceRoot(actionCmd);
48183
48347
  const doc = requireYaml(root);
48184
- const file = path12.join((0, import_core18.epicsRoot)(root, doc), epicId, "state.json");
48348
+ const file = path12.join((0, import_core19.epicsRoot)(root, doc), epicId, "state.json");
48185
48349
  if (!fs10.existsSync(file)) {
48186
48350
  console.error(import_chalk15.default.red(`No epic "${epicId}" \u2014 expected ${file}`));
48187
48351
  process.exit(1);
@@ -48195,7 +48359,7 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48195
48359
  return;
48196
48360
  }
48197
48361
  if (value === void 0) {
48198
- const on = (0, import_core18.epicStrictMode)(state);
48362
+ const on = (0, import_core19.epicStrictMode)(state);
48199
48363
  console.log(`${epicId}: strict_mode ${on ? import_chalk15.default.bold("true") : import_chalk15.default.bold("false")}`);
48200
48364
  console.log(import_chalk15.default.dim(on ? " Phases work at full depth. Set false for an epic the size of one task." : " Phases stay proportional to the work \u2014 no invented NFR / risk sections."));
48201
48365
  return;
@@ -48207,7 +48371,7 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48207
48371
  console.error(import_chalk15.default.red(`Expected on|off, got "${value}".`));
48208
48372
  process.exit(1);
48209
48373
  }
48210
- state[import_core18.STRICT_MODE_KEY] = truthy.includes(v);
48374
+ state[import_core19.STRICT_MODE_KEY] = truthy.includes(v);
48211
48375
  fs10.writeFileSync(file, JSON.stringify(state, null, 2) + "\n", "utf8");
48212
48376
  console.log(import_chalk15.default.green("\u2714") + ` ${epicId}: strict_mode ${truthy.includes(v)}`);
48213
48377
  console.log(import_chalk15.default.dim(" Takes effect on the next phase run \u2014 nothing already written changes."));
@@ -48222,7 +48386,7 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48222
48386
  const root = resolveWorkspaceRoot(actionCmd);
48223
48387
  const doc = requireYaml(root);
48224
48388
  const { runState, pipelineCfg } = requireEditableEpic(root, doc, epicId);
48225
- const plan = runEdit(() => (0, import_core18.planAddEpicStep)({
48389
+ const plan = runEdit(() => (0, import_core19.planAddEpicStep)({
48226
48390
  runState,
48227
48391
  pipeline: pipelineCfg,
48228
48392
  step: {
@@ -48247,7 +48411,7 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48247
48411
  const root = resolveWorkspaceRoot(actionCmd);
48248
48412
  const doc = requireYaml(root);
48249
48413
  const { runState, pipelineCfg } = requireEditableEpic(root, doc, epicId);
48250
- const plan = runEdit(() => (0, import_core18.planRemoveEpicStep)({ runState, pipeline: pipelineCfg, step }));
48414
+ const plan = runEdit(() => (0, import_core19.planRemoveEpicStep)({ runState, pipeline: pipelineCfg, step }));
48251
48415
  writeEdit(root, doc, plan);
48252
48416
  console.log(import_chalk15.default.green("\u2714") + ` Removed step ${import_chalk15.default.bold(plan.stepId)} from ${import_chalk15.default.bold(pipelineCfg.id)}`);
48253
48417
  printSteps(plan);
@@ -48256,7 +48420,7 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48256
48420
  const root = resolveWorkspaceRoot(actionCmd);
48257
48421
  const doc = requireYaml(root);
48258
48422
  const { runState, pipelineCfg } = requireEditableEpic(root, doc, epicId);
48259
- const plan = runEdit(() => (0, import_core18.planSetEpicStepGates)({
48423
+ const plan = runEdit(() => (0, import_core19.planSetEpicStepGates)({
48260
48424
  runState,
48261
48425
  pipeline: pipelineCfg,
48262
48426
  step,
@@ -48276,14 +48440,14 @@ ${plan.length} pipeline(s) would move. Re-run without --dry-run.`));
48276
48440
  const arrow = c.to ? import_chalk15.default.yellow("on") : import_chalk15.default.dim("off");
48277
48441
  console.log(` ${import_chalk15.default.dim(c.gate)} ${import_chalk15.default.dim(String(c.from))} \u2192 ${arrow}`);
48278
48442
  }
48279
- const note = (0, import_core18.describeGateEffect)(plan.stepStatus, plan.changes);
48443
+ const note = (0, import_core19.describeGateEffect)(plan.stepStatus, plan.changes);
48280
48444
  if (note) {
48281
48445
  console.log(import_chalk15.default.yellow(` The step is ${plan.stepStatus}. `) + import_chalk15.default.dim(note));
48282
48446
  }
48283
48447
  });
48284
48448
  }
48285
48449
  function requireEditableEpic(root, doc, epicId) {
48286
- const runState = import_core18.RunStateStore.load(root, epicId);
48450
+ const runState = import_core19.RunStateStore.load(root, epicId);
48287
48451
  if (!runState) {
48288
48452
  console.error(import_chalk15.default.red(`Epic "${epicId}" has no run state at .aidlc/runs/${epicId}.json.`));
48289
48453
  console.error(import_chalk15.default.dim(" Only a pipeline-backed epic has a step list to edit."));
@@ -48307,7 +48471,7 @@ function runEdit(plan) {
48307
48471
  try {
48308
48472
  return plan();
48309
48473
  } catch (err) {
48310
- if (err instanceof import_core18.EpicStepEditError) {
48474
+ if (err instanceof import_core19.EpicStepEditError) {
48311
48475
  console.error(import_chalk15.default.red(err.message));
48312
48476
  process.exit(1);
48313
48477
  }
@@ -48316,7 +48480,7 @@ function runEdit(plan) {
48316
48480
  }
48317
48481
  function writeEdit(root, doc, plan) {
48318
48482
  const before = JSON.parse(JSON.stringify(doc.pipelines));
48319
- (0, import_core18.commitEpicStepEdit)({
48483
+ (0, import_core19.commitEpicStepEdit)({
48320
48484
  workspaceRoot: root,
48321
48485
  doc,
48322
48486
  plan,
@@ -48335,13 +48499,13 @@ function writePipeline(root, doc, pipeline) {
48335
48499
  );
48336
48500
  let config;
48337
48501
  try {
48338
- config = (0, import_core18.validateWorkspace)(doc, ".aidlc/workspace.yaml");
48502
+ config = (0, import_core19.validateWorkspace)(doc, ".aidlc/workspace.yaml");
48339
48503
  } catch (err) {
48340
48504
  console.error(import_chalk15.default.red("The edited pipeline fails workspace validation \u2014 nothing was written:"));
48341
48505
  console.error(import_chalk15.default.dim(err instanceof Error ? err.message : String(err)));
48342
48506
  process.exit(1);
48343
48507
  }
48344
- const issues = (0, import_core18.collectWorkspaceRefIssues)(config).filter((i) => i.path.startsWith(`pipelines.${pipeline.id}.`));
48508
+ const issues = (0, import_core19.collectWorkspaceRefIssues)(config).filter((i) => i.path.startsWith(`pipelines.${pipeline.id}.`));
48345
48509
  if (issues.length > 0) {
48346
48510
  console.error(import_chalk15.default.red("The edited step references something the workspace does not define \u2014 nothing was written:"));
48347
48511
  for (const issue of issues) {
@@ -48355,7 +48519,7 @@ function printSteps(plan) {
48355
48519
  const { runState } = plan;
48356
48520
  runState.steps.forEach((s, i) => {
48357
48521
  const marker = i === runState.currentStepIdx ? import_chalk15.default.yellow("\u25B6") : " ";
48358
- const label = i === plan.index ? import_chalk15.default.bold((0, import_core18.stepIdentity)(s)) : import_chalk15.default.dim((0, import_core18.stepIdentity)(s));
48522
+ const label = i === plan.index ? import_chalk15.default.bold((0, import_core19.stepIdentity)(s)) : import_chalk15.default.dim((0, import_core19.stepIdentity)(s));
48359
48523
  console.log(` ${marker} ${import_chalk15.default.dim(String(i))} ${label} ${import_chalk15.default.dim("(" + s.status + ")")}`);
48360
48524
  });
48361
48525
  if (plan.previousCurrentStepIdx !== runState.currentStepIdx) {
@@ -48375,6 +48539,9 @@ function printEpicDetail(epic) {
48375
48539
  if (epic.description) {
48376
48540
  console.log(import_chalk15.default.dim(" desc: ") + epic.description);
48377
48541
  }
48542
+ if (epic.tags.length) {
48543
+ console.log(import_chalk15.default.dim(" tags: ") + import_chalk15.default.cyan(epic.tags.join(" ")));
48544
+ }
48378
48545
  if (epic.pipeline) {
48379
48546
  console.log(import_chalk15.default.dim(" pipeline: ") + epic.pipeline);
48380
48547
  }
@@ -48424,7 +48591,7 @@ function truncate(s, max) {
48424
48591
 
48425
48592
  // src/commands/recipe.ts
48426
48593
  var import_chalk16 = __toESM(require_source());
48427
- var import_core19 = __toESM(require_dist());
48594
+ var import_core20 = __toESM(require_dist());
48428
48595
  function registerRecipe(program3) {
48429
48596
  const cmd = program3.command("recipe").description("Manage task-type recipes in workspace.yaml");
48430
48597
  cmd.command("init").description("Back-fill task-type recipes from the existing pipeline (enables --brief suggestion)").option("--dry-run", "print what would be added without writing").action((opts, actionCmd) => {
@@ -48434,7 +48601,7 @@ function registerRecipe(program3) {
48434
48601
  console.log(import_chalk16.default.dim(`Workspace already has ${doc.recipes.length} recipe(s) \u2014 nothing to do.`));
48435
48602
  return;
48436
48603
  }
48437
- const recipes = (0, import_core19.planRecipeMigration)(doc);
48604
+ const recipes = (0, import_core20.planRecipeMigration)(doc);
48438
48605
  if (!recipes) {
48439
48606
  console.error(import_chalk16.default.yellow(
48440
48607
  "No recipes could be derived. A pipeline whose steps overlap the built-in\nSDLC phases (plan, design, implement, execute-test, \u2026) is required.\nApply the SDLC preset first: aidlc preset apply sdlc"
@@ -48465,7 +48632,7 @@ var path13 = __toESM(require("path"));
48465
48632
  var import_child_process4 = require("child_process");
48466
48633
  var readline = __toESM(require("readline"));
48467
48634
  var import_chalk17 = __toESM(require_source());
48468
- var import_core20 = __toESM(require_dist());
48635
+ var import_core21 = __toESM(require_dist());
48469
48636
  var OBSERVE_PORT = 4981;
48470
48637
  var OBSERVE_HOST = "127.0.0.1";
48471
48638
  var OBSERVE_BASE = `http://${OBSERVE_HOST}:${OBSERVE_PORT}`;
@@ -48477,10 +48644,10 @@ function aidlcDataDir() {
48477
48644
  return path13.join(os2.homedir(), ".aidlc", "observe-data");
48478
48645
  }
48479
48646
  function settingsPath() {
48480
- return path13.join((0, import_core20.claudeConfigDir)(), "settings.json");
48647
+ return path13.join((0, import_core21.claudeConfigDir)(), "settings.json");
48481
48648
  }
48482
48649
  function pluginsRoot() {
48483
- return path13.join((0, import_core20.claudeConfigDir)(), "plugins");
48650
+ return path13.join((0, import_core21.claudeConfigDir)(), "plugins");
48484
48651
  }
48485
48652
  function parsePluginList(out2) {
48486
48653
  if (!/agents-observe/i.test(out2)) return null;
@@ -48838,14 +49005,14 @@ opening ${DASHBOARD_URL} \u2026`));
48838
49005
  // src/commands/ask.ts
48839
49006
  var import_child_process5 = require("child_process");
48840
49007
  var import_chalk18 = __toESM(require_source());
48841
- var import_core21 = __toESM(require_dist());
49008
+ var import_core22 = __toESM(require_dist());
48842
49009
  var ASK_SYSTEM_PROMPT = `You are the AIDLC assistant, embedded in the \`aidlc\` CLI.
48843
49010
  Answer the user's question about AIDLC concisely and practically \u2014 prefer the
48844
49011
  exact command, flag, or button name over abstract description. When the user is
48845
49012
  getting started, give them the concrete next command to run. Use the reference
48846
49013
  below as ground truth and never invent commands or settings not listed in it.
48847
49014
 
48848
- ${import_core21.AIDLC_KNOWLEDGE}`;
49015
+ ${import_core22.AIDLC_KNOWLEDGE}`;
48849
49016
  function registerAsk(program3) {
48850
49017
  program3.command("ask <prompt...>").description("ask Claude about AIDLC \u2014 setup, concepts, and CLI commands").action(async (promptParts, _opts, cmd) => {
48851
49018
  const userPrompt = promptParts.join(" ").trim();
@@ -48861,7 +49028,7 @@ function registerAsk(program3) {
48861
49028
  process.exit(1);
48862
49029
  }
48863
49030
  const root = resolveWorkspaceRoot(cmd);
48864
- const runner = new import_core21.DefaultRunner();
49031
+ const runner = new import_core22.DefaultRunner();
48865
49032
  process.stderr.write("\n");
48866
49033
  const result = await runner.run({
48867
49034
  skill: ASK_SYSTEM_PROMPT,
@@ -48881,31 +49048,31 @@ function registerAsk(program3) {
48881
49048
  }
48882
49049
 
48883
49050
  // src/commands/guide.ts
48884
- var import_core22 = __toESM(require_dist());
49051
+ var import_core23 = __toESM(require_dist());
48885
49052
  function registerGuide(program3) {
48886
49053
  program3.command("guide").description("step-by-step getting-started reference (no LLM, no cost)").action(() => {
48887
- console.log(import_core22.AIDLC_CLI_GUIDE_TEXT);
49054
+ console.log(import_core23.AIDLC_CLI_GUIDE_TEXT);
48888
49055
  });
48889
49056
  }
48890
49057
 
48891
49058
  // src/commands/globals.ts
48892
49059
  var import_chalk19 = __toESM(require_source());
48893
- var import_core23 = __toESM(require_dist());
49060
+ var import_core24 = __toESM(require_dist());
48894
49061
  function registerGlobals(program3) {
48895
49062
  const cmd = program3.command("globals").description("Install / uninstall built-in workflow agents + skills under ~/.claude/");
48896
49063
  cmd.command("status").description("Show which built-in workflows are installed globally").option("--json", "Output raw JSON").action((opts) => {
48897
49064
  const root = cliTemplatesRoot();
48898
- const rows = import_core23.BUILTIN_WORKFLOWS.map((w) => ({
49065
+ const rows = import_core24.BUILTIN_WORKFLOWS.map((w) => ({
48899
49066
  id: w.id,
48900
49067
  name: w.name,
48901
- installed: (0, import_core23.isWorkflowGloballyInstalled)(root, w.id)
49068
+ installed: (0, import_core24.isWorkflowGloballyInstalled)(root, w.id)
48902
49069
  }));
48903
49070
  if (opts.json) {
48904
49071
  console.log(JSON.stringify(rows, null, 2));
48905
49072
  return;
48906
49073
  }
48907
49074
  console.log(import_chalk19.default.bold(`
48908
- Built-in workflow globals (${(0, import_core23.claudeConfigDir)()})`));
49075
+ Built-in workflow globals (${(0, import_core24.claudeConfigDir)()})`));
48909
49076
  for (const r of rows) {
48910
49077
  const mark = r.installed ? import_chalk19.default.green("\u2714 installed") : import_chalk19.default.dim("\xB7 not installed");
48911
49078
  console.log(` ${import_chalk19.default.cyan(r.id.padEnd(24))} ${mark}`);
@@ -48914,22 +49081,22 @@ Built-in workflow globals (${(0, import_core23.claudeConfigDir)()})`));
48914
49081
  });
48915
49082
  cmd.command("install [ids...]").description("Install built-in workflow globals (default: the standard workflows)").action((ids) => {
48916
49083
  const root = cliTemplatesRoot();
48917
- const targetIds = ids.length > 0 ? ids : [...import_core23.DEFAULT_GLOBAL_WORKFLOW_IDS];
48918
- const known = new Set(import_core23.BUILTIN_WORKFLOWS.map((w) => w.id));
49084
+ const targetIds = ids.length > 0 ? ids : [...import_core24.DEFAULT_GLOBAL_WORKFLOW_IDS];
49085
+ const known = new Set(import_core24.BUILTIN_WORKFLOWS.map((w) => w.id));
48919
49086
  const unknown = targetIds.filter((id) => !known.has(id));
48920
49087
  if (unknown.length > 0) {
48921
49088
  console.error(import_chalk19.default.red(`Unknown workflow id(s): ${unknown.join(", ")}`));
48922
49089
  console.error(import_chalk19.default.dim(`Known: ${[...known].join(", ")}`));
48923
49090
  process.exit(1);
48924
49091
  }
48925
- const reports = (0, import_core23.installWorkflowGlobalsByIds)(root, targetIds);
49092
+ const reports = (0, import_core24.installWorkflowGlobalsByIds)(root, targetIds);
48926
49093
  for (const r of reports) {
48927
49094
  console.log(
48928
49095
  import_chalk19.default.green("\u2714") + ` ${import_chalk19.default.bold(r.workflow)} \u2014 wrote ${r.written.length}, skipped ${r.skipped.length}`
48929
49096
  );
48930
49097
  }
48931
- console.log(import_chalk19.default.dim(` Files live under ${(0, import_core23.claudeConfigDir)()}/{agents,skills}`));
48932
- const ann = (0, import_core23.installAnnotationTools)(root);
49098
+ console.log(import_chalk19.default.dim(` Files live under ${(0, import_core24.claudeConfigDir)()}/{agents,skills}`));
49099
+ const ann = (0, import_core24.installAnnotationTools)(root);
48933
49100
  if (ann.installed) {
48934
49101
  console.log(
48935
49102
  import_chalk19.default.green("\u2714") + " annotation tooling \u2014 renderer + annotron + epic-memory + /annotate-artifact, /epic-context"
@@ -48940,8 +49107,8 @@ Built-in workflow globals (${(0, import_core23.claudeConfigDir)()})`));
48940
49107
  });
48941
49108
  cmd.command("uninstall [ids...]").description("Remove AIDLC-installed workflow globals (preserves files shared by other installed workflows)").action((ids) => {
48942
49109
  const root = cliTemplatesRoot();
48943
- const targetIds = ids.length > 0 ? ids : [...import_core23.DEFAULT_GLOBAL_WORKFLOW_IDS];
48944
- const reports = (0, import_core23.uninstallWorkflowGlobalsByIds)(targetIds, void 0, root);
49110
+ const targetIds = ids.length > 0 ? ids : [...import_core24.DEFAULT_GLOBAL_WORKFLOW_IDS];
49111
+ const reports = (0, import_core24.uninstallWorkflowGlobalsByIds)(targetIds, void 0, root);
48945
49112
  let removed = 0;
48946
49113
  for (const r of reports) {
48947
49114
  removed += r.removed.length;
@@ -48955,19 +49122,19 @@ Built-in workflow globals (${(0, import_core23.claudeConfigDir)()})`));
48955
49122
  });
48956
49123
  const hook = cmd.command("memory-hook <action>").description("Toggle the epic-memory auto-load hook (action: enable | disable | status)").action((action) => {
48957
49124
  if (action === "status") {
48958
- const on = (0, import_core23.isEpicMemoryHookEnabled)();
49125
+ const on = (0, import_core24.isEpicMemoryHookEnabled)();
48959
49126
  console.log(`Epic-memory hook: ${on ? import_chalk19.default.green("enabled") : import_chalk19.default.dim("disabled")}`);
48960
49127
  return;
48961
49128
  }
48962
49129
  if (action === "enable") {
48963
- (0, import_core23.installAnnotationTools)(cliTemplatesRoot());
48964
- const r = (0, import_core23.setEpicMemoryHook)(true);
49130
+ (0, import_core24.installAnnotationTools)(cliTemplatesRoot());
49131
+ const r = (0, import_core24.setEpicMemoryHook)(true);
48965
49132
  console.log(import_chalk19.default.green("\u2714") + ` Epic-memory hook enabled${r.changed ? "" : " (already on)"}.`);
48966
49133
  console.log(import_chalk19.default.dim(" A prompt that mentions an epic now auto-loads its epic-memory.json."));
48967
49134
  return;
48968
49135
  }
48969
49136
  if (action === "disable") {
48970
- const r = (0, import_core23.setEpicMemoryHook)(false);
49137
+ const r = (0, import_core24.setEpicMemoryHook)(false);
48971
49138
  console.log(import_chalk19.default.yellow("\u2193") + ` Epic-memory hook disabled${r.changed ? "" : " (already off)"}.`);
48972
49139
  return;
48973
49140
  }
@@ -49149,14 +49316,14 @@ function tryInstallSkill(root) {
49149
49316
  var fs13 = __toESM(require("fs"));
49150
49317
  var path15 = __toESM(require("path"));
49151
49318
  var import_chalk21 = __toESM(require_source());
49152
- var import_core24 = __toESM(require_dist());
49319
+ var import_core25 = __toESM(require_dist());
49153
49320
  var INCIDENT_RECIPE = "native-incident";
49154
49321
  var FOLLOW_UP_RECIPE = "native-full";
49155
49322
  function registerMaintain(program3) {
49156
49323
  const cmd = program3.command("maintain").description("Stage 6 \u2014 turn a production signal into an incident epic (AI-Native SDLC)").option("-s, --signal <file>", 'signal JSON file ("-" reads stdin)').option("--recipe <id>", `recipe to assemble the incident pipeline from (default: ${INCIDENT_RECIPE})`).option("--pipeline <id>", "use an existing pipeline as-is instead of a recipe").option("--from <pipelineId>", "override the recipe's source pipeline").option("--epic <id>", "epic id (default: derived from the symptom, e.g. INC-CHECKOUT-RETURNS-500)").option("--prefix <prefix>", "id prefix when deriving (default: INC)").option("--json", "output the result as JSON").action((opts, actionCmd) => {
49157
49324
  runMaintain(opts, actionCmd);
49158
49325
  });
49159
- cmd.command("follow-up <incidentEpic>").description("Open the epic this incident calls for, with its intent.md already written").option("-s, --signal <file>", `signal JSON (default: the incident epic's ${import_core24.SIGNAL_FILE})`).option("--recipe <id>", `recipe for the new epic (default: ${FOLLOW_UP_RECIPE})`).option("--pipeline <id>", "use an existing pipeline as-is instead of a recipe").option("--from <pipelineId>", "override the recipe's source pipeline").option("--epic <id>", "id for the new epic (default: <incidentEpic>-FIX)").option("--intent <file>", "use this markdown as intent.md verbatim, instead of rendering one").option("--problem <text>", "the problem in the user's terms \u2014 no solution language").option("--who-hurts <text>", "the specific role doing the specific task").option("--cost <text>", "what it costs them (default: the signal's scope)").option("--done <text>", "the observable condition that says this cannot recur").option("--question <text>", "an open question for the human gate (repeatable)", collect, []).option("--json", "output the result as JSON").action((incidentEpic, opts, actionCmd) => {
49326
+ cmd.command("follow-up <incidentEpic>").description("Open the epic this incident calls for, with its intent.md already written").option("-s, --signal <file>", `signal JSON (default: the incident epic's ${import_core25.SIGNAL_FILE})`).option("--recipe <id>", `recipe for the new epic (default: ${FOLLOW_UP_RECIPE})`).option("--pipeline <id>", "use an existing pipeline as-is instead of a recipe").option("--from <pipelineId>", "override the recipe's source pipeline").option("--epic <id>", "id for the new epic (default: <incidentEpic>-FIX)").option("--intent <file>", "use this markdown as intent.md verbatim, instead of rendering one").option("--problem <text>", "the problem in the user's terms \u2014 no solution language").option("--who-hurts <text>", "the specific role doing the specific task").option("--cost <text>", "what it costs them (default: the signal's scope)").option("--done <text>", "the observable condition that says this cannot recur").option("--question <text>", "an open question for the human gate (repeatable)", collect, []).option("--json", "output the result as JSON").action((incidentEpic, opts, actionCmd) => {
49160
49327
  runFollowUp(incidentEpic, opts, actionCmd);
49161
49328
  });
49162
49329
  }
@@ -49170,14 +49337,14 @@ function runMaintain(opts, actionCmd) {
49170
49337
  const root = resolveWorkspaceRoot(actionCmd);
49171
49338
  const doc = requireYaml(root);
49172
49339
  const signal = loadSignal(opts.signal);
49173
- const epicId = opts.epic?.trim() || (0, import_core24.followUpEpicId)(signal, { prefix: opts.prefix, taken: (0, import_core24.existingEpicIds)(root, doc) });
49340
+ const epicId = opts.epic?.trim() || (0, import_core25.followUpEpicId)(signal, { prefix: opts.prefix, taken: (0, import_core25.existingEpicIds)(root, doc) });
49174
49341
  const pipelineCfg = resolvePipeline(root, doc, {
49175
49342
  pipeline: opts.pipeline,
49176
49343
  recipe: opts.recipe ?? INCIDENT_RECIPE,
49177
49344
  from: opts.from
49178
49345
  }, epicId);
49179
49346
  try {
49180
- const result = (0, import_core24.openIncidentEpic)({
49347
+ const result = (0, import_core25.openIncidentEpic)({
49181
49348
  workspaceRoot: root,
49182
49349
  doc,
49183
49350
  signal,
@@ -49212,16 +49379,16 @@ function runFollowUp(incidentEpic, opts, actionCmd) {
49212
49379
  if (opts.signal) {
49213
49380
  signal = loadSignal(opts.signal);
49214
49381
  } else {
49215
- const raw = (0, import_core24.readEpicSignal)(root, doc, incidentEpic);
49382
+ const raw = (0, import_core25.readEpicSignal)(root, doc, incidentEpic);
49216
49383
  if (raw === null) {
49217
49384
  fail2(
49218
- `Epic "${incidentEpic}" has no ${import_core24.SIGNAL_FILE} to open a follow-up from.`,
49385
+ `Epic "${incidentEpic}" has no ${import_core25.SIGNAL_FILE} to open a follow-up from.`,
49219
49386
  `Pass one with --signal <file>, or register the incident first: aidlc maintain --signal <file>`
49220
49387
  );
49221
49388
  }
49222
- signal = parseOrFail(raw, path15.join(incidentEpic, import_core24.SIGNAL_FILE));
49389
+ signal = parseOrFail(raw, path15.join(incidentEpic, import_core25.SIGNAL_FILE));
49223
49390
  }
49224
- const epicId = opts.epic?.trim() || (0, import_core24.followUpIdFor)(incidentEpic, (0, import_core24.existingEpicIds)(root, doc));
49391
+ const epicId = opts.epic?.trim() || (0, import_core25.followUpIdFor)(incidentEpic, (0, import_core25.existingEpicIds)(root, doc));
49225
49392
  const pipelineCfg = resolvePipeline(root, doc, {
49226
49393
  pipeline: opts.pipeline,
49227
49394
  recipe: opts.recipe ?? FOLLOW_UP_RECIPE,
@@ -49243,7 +49410,7 @@ function runFollowUp(incidentEpic, opts, actionCmd) {
49243
49410
  intentMarkdown = fs13.readFileSync(opts.intent, "utf8");
49244
49411
  }
49245
49412
  try {
49246
- const result = (0, import_core24.openFollowUpEpic)({
49413
+ const result = (0, import_core25.openFollowUpEpic)({
49247
49414
  workspaceRoot: root,
49248
49415
  doc,
49249
49416
  signal,
@@ -49282,7 +49449,7 @@ function resolvePipeline(root, doc, opts, epicId) {
49282
49449
  }
49283
49450
  let config;
49284
49451
  try {
49285
- config = (0, import_core24.validateWorkspace)(doc, ".aidlc/workspace.yaml");
49452
+ config = (0, import_core25.validateWorkspace)(doc, ".aidlc/workspace.yaml");
49286
49453
  } catch (err) {
49287
49454
  fail2(
49288
49455
  "workspace.yaml is invalid \u2014 fix it before running stage 6:",
@@ -49301,26 +49468,26 @@ function resolvePipeline(root, doc, opts, epicId) {
49301
49468
  recipe.from = opts.from;
49302
49469
  }
49303
49470
  }
49304
- const pipelineId = (0, import_core24.recipePipelineId)({
49471
+ const pipelineId = (0, import_core25.recipePipelineId)({
49305
49472
  recipeId: opts.recipe,
49306
49473
  epicId,
49307
49474
  taken: existingIds(doc.pipelines)
49308
49475
  });
49309
49476
  let pipelineCfg;
49310
49477
  try {
49311
- pipelineCfg = (0, import_core24.assemblePipeline)(config, { recipeId: opts.recipe, pipelineId });
49478
+ pipelineCfg = (0, import_core25.assemblePipeline)(config, { recipeId: opts.recipe, pipelineId });
49312
49479
  } catch (err) {
49313
- if (err instanceof import_core24.PipelineAssembleError) {
49480
+ if (err instanceof import_core25.PipelineAssembleError) {
49314
49481
  fail2("Could not assemble pipeline: " + err.message);
49315
49482
  }
49316
49483
  throw err;
49317
49484
  }
49318
49485
  doc.pipelines.push(pipelineCfg);
49319
49486
  if (epicId) {
49320
- (0, import_core24.stageEpicPipeline)(doc, pipelineCfg.id, epicId);
49487
+ (0, import_core25.stageEpicPipeline)(doc, pipelineCfg.id, epicId);
49321
49488
  }
49322
49489
  try {
49323
- (0, import_core24.validateWorkspace)(doc, ".aidlc/workspace.yaml");
49490
+ (0, import_core25.validateWorkspace)(doc, ".aidlc/workspace.yaml");
49324
49491
  } catch (err) {
49325
49492
  fail2(
49326
49493
  "Assembled pipeline failed validation \u2014 not written:",
@@ -49342,9 +49509,9 @@ function loadSignal(file) {
49342
49509
  }
49343
49510
  function parseOrFail(raw, where) {
49344
49511
  try {
49345
- return (0, import_core24.parseSignal)(raw);
49512
+ return (0, import_core25.parseSignal)(raw);
49346
49513
  } catch (err) {
49347
- if (err instanceof import_core24.SignalParseError) {
49514
+ if (err instanceof import_core25.SignalParseError) {
49348
49515
  fail2(`${where}: ${err.message}`, EXAMPLE_SIGNAL);
49349
49516
  }
49350
49517
  throw err;
@@ -49372,7 +49539,7 @@ function fail2(message, detail) {
49372
49539
  process.exit(1);
49373
49540
  }
49374
49541
  function rethrow(err) {
49375
- if (err instanceof import_core24.EpicScaffoldError) {
49542
+ if (err instanceof import_core25.EpicScaffoldError) {
49376
49543
  fail2(err.message);
49377
49544
  }
49378
49545
  throw err;
@@ -49386,7 +49553,7 @@ program2.name("aidlc").description("AIDLC terminal CLI \u2014 drive workspace.ya
49386
49553
  setQuiet(true);
49387
49554
  }
49388
49555
  const raw = thisCommand.opts().workspace ?? process.env.AIDLC_WORKSPACE ?? process.cwd();
49389
- (0, import_core25.activateBackendFromWorkspace)(path16.resolve(raw), (text) => load(text));
49556
+ (0, import_core26.activateBackendFromWorkspace)(path16.resolve(raw), (text) => load(text));
49390
49557
  });
49391
49558
  registerInit(program2);
49392
49559
  registerValidate(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delete101020/aidlc",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "AIDLC Native CLI, a fork of AIDLC — drive Claude through the six-stage AI-Native SDLC Playbook: Plan → Design → Build → Test → Deploy → Maintain, with artifact gates, requirements traceability, an incident loop back to Plan, and Spec Kit support.",
5
5
  "license": "MIT",
6
6
  "repository": {