@cardor/agent-harness-kit 1.6.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -592,41 +592,47 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
592
592
  | `actions.record_tool` | `actionId, toolName, argsJson?, resultSummary?` | Register a tool call. The **only** way to populate the Tools dashboard |
593
593
  | `docs.search` | `query` | Search the `docsPath` folder for content matching the query |
594
594
  | `tasks.acceptance_get` | `taskId` | Returns all acceptance criteria for a task with their `id`, `task_id`, `criterion` text, and `met` status. Use the returned `id` values with `tasks.acceptance.update` |
595
+ | `deps.snapshot` | _(none)_ | Snapshot current `package.json` dependencies to `.harness/deps-lock.json` |
596
+ | `deps.check` | _(none)_ | Compare current `package.json` against `.harness/deps-lock.json`. Returns `{ significant, added, removed, majorBumps, advisory }` |
595
597
 
596
598
  ---
597
599
 
598
600
  ## Agent roles
599
601
 
600
- | Role | Responsibility |
601
- | ------------ | ------------------------------------------------------------------------------------------------- |
602
- | **lead** | Decomposes the task into a plan, assigns sub-agents. Does not write code or read source files. |
603
- | **explorer** | Reads and maps the codebase. Never writes files. Records every file read. |
604
- | **builder** | Implements the plan. Only writes to `writablePaths`. Records every file modified. |
605
- | **reviewer** | Verifies all acceptance criteria are met. Approves or blocks. Runs health check before approving. |
602
+ | Role | Responsibility |
603
+ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
604
+ | **lead** | Decomposes the task into a plan, assigns sub-agents. Does not write code or read source files. |
605
+ | **explorer** | Reads and maps the codebase. Never writes files. Records every file read. |
606
+ | **consultant** | Provides structured technical advisory after explorer. Runs conditionally. Never writes code. Writes advisory to harness via actions.write. |
607
+ | **builder** | Implements the plan. Only writes to `writablePaths`. Records every file modified. |
608
+ | **reviewer** | Verifies all acceptance criteria are met. Approves or blocks. Runs health check before approving. |
606
609
 
607
610
  ### MCP tool permissions by role
608
611
 
609
612
  Each agent role has a scoped set of MCP tools enforced through the agent definition files.
610
613
 
611
- | Tool | lead | explorer | builder | reviewer |
612
- |---|:---:|:---:|:---:|:---:|
613
- | `tasks.get` | ✅ | ✅ | ✅ | ✅ |
614
- | `tasks.claim` | ✅ | ✅ | ✅ | ✅ |
615
- | `tasks.add` | ✅ | ❌ | ✅ | ✅ |
616
- | `tasks.update` | ✅ | ❌ | ✅ | ✅ |
617
- | `tasks.edit` | ✅ | ❌ | ✅ | ✅ |
618
- | `tasks.archive` / `unarchive` | ✅ | ❌ | ✅ | ✅ |
619
- | `tasks.acceptance_get` | ✅ | ✅ | ✅ | ✅ |
620
- | `tasks.acceptance.update` | ❌ | ❌ | ❌ | ✅ |
621
- | `actions.*` (all 6) | ✅ | ✅ | ✅ | ✅ |
622
- | `docs.search` | ✅ | ✅ | ✅ | ✅ |
623
- | `permissions.check` | ✅ | ✅ | ✅ | ✅ |
614
+ | Tool | lead | explorer | consultant | builder | reviewer |
615
+ |---|:---:|:---:|:---:|:---:|:---:|
616
+ | `tasks.get` | ✅ | ✅ | ✅ | ✅ | ✅ |
617
+ | `tasks.claim` | ✅ | ✅ | ❌ | ✅ | ✅ |
618
+ | `tasks.add` | ✅ | ❌ | ❌ | ✅ | ✅ |
619
+ | `tasks.update` | ✅ | ❌ | ❌ | ✅ | ✅ |
620
+ | `tasks.edit` | ✅ | ❌ | ❌ | ✅ | ✅ |
621
+ | `tasks.archive` / `unarchive` | ✅ | ❌ | ❌ | ✅ | ✅ |
622
+ | `tasks.acceptance_get` | ✅ | ✅ | ❌ | ✅ | ✅ |
623
+ | `tasks.acceptance.update` | ❌ | ❌ | ❌ | ❌ | ✅ |
624
+ | `actions.*` (all 6) | ✅ | ✅ | ✅ | ✅ | ✅ |
625
+ | `docs.search` | ✅ | ✅ | ❌ | ✅ | ✅ |
626
+ | `permissions.check` | ✅ | ✅ | ❌ | ✅ | ✅ |
627
+ | `deps.snapshot` | ❌ | ❌ | ✅ | ❌ | ❌ |
628
+ | `deps.check` | ❌ | ❌ | ✅ | ❌ | ❌ |
624
629
 
625
630
  **explorer** is read-only for task state — can query but cannot mutate status or mark criteria.
626
631
  **reviewer** is the only role that can mark acceptance criteria as met (`tasks.acceptance.update`).
627
- **lead** and **builder** have identical access, both excluding `tasks.acceptance.update`.
632
+ **lead** and **builder** have identical access, both excluding `tasks.acceptance.update`.
633
+ **consultant** is advisory-only — reads code, writes to harness, and can call deps tools. Never modifies the codebase.
628
634
 
629
- `permissions.check` compares each `.claude/agents/*.md` tool list against the canonical constants in the package. Returns `{ in_sync: bool, agents: { lead, explorer, builder, reviewer } }` with per-agent `missing` and `extra` arrays. Run `ahk build --sync` to fix any drift.
635
+ `permissions.check` compares each `.claude/agents/*.md` tool list against the canonical constants in the package. Returns `{ in_sync: bool, agents: { lead, explorer, consultant, builder, reviewer } }` with per-agent `missing` and `extra` arrays. Run `ahk build --sync` to fix any drift.
630
636
 
631
637
  ---
632
638
 
@@ -75,7 +75,7 @@ If you touched 5 files and made 12 tool calls, there must be 5 `actions.record_f
75
75
  actions.get(taskId)
76
76
  ```
77
77
 
78
- Read the lead's `result` section (the plan) and the explorer's `result` section (the analysis). Do not start until you understand both.
78
+ Read ALL previous actions via `actions.get(taskId)` including the lead's plan, the explorer's analysis, and the consultant's advisory (if present). Do not rely on the lead summary alone. This includes the consultant's advisory (if present) — read it before writing any code.
79
79
 
80
80
  ### 2. Register your action
81
81
 
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: consultant
3
+ description: >
4
+ Technical advisor agent for {{projectName}}. Runs after the explorer and before the builder.
5
+ Provides structured advisory — patterns, best practices, warnings, and risks — written
6
+ directly to the harness so the builder can read it via actions.get. Never writes code.
7
+ tools:
8
+ - Read
9
+ - Bash
10
+ ---
11
+
12
+ # Consultant Agent — {{projectName}}
13
+
14
+ You are the **consultant agent** for `{{projectName}}`. Your job is to provide structured technical advisory based on the explorer's findings. You do not write code or modify files.
15
+
16
+ ---
17
+
18
+ ## !! ABSOLUTE CONSTRAINT !!
19
+
20
+ **YOU ARE FORBIDDEN FROM MODIFYING THE CODEBASE IN ANY WAY.**
21
+
22
+ Read files. Think. Write your advisory to the harness. That is all.
23
+
24
+ ---
25
+
26
+ ## Responsibilities
27
+
28
+ - Read the explorer's output via `actions.get(taskId)`
29
+ - Analyse the relevant code sections identified by the explorer
30
+ - Produce a structured advisory covering: patterns to follow, pitfalls to avoid, best practices, risks
31
+ - Record your advisory directly in the harness so the builder reads it without lead filtering
32
+
33
+ ---
34
+
35
+ ## Workflow
36
+
37
+ ### 1. Read context
38
+
39
+ ```
40
+ actions.get(taskId) → read explorer's analysis and lead's plan
41
+ ```
42
+
43
+ ### 2. Analyse
44
+
45
+ Read the files the explorer mapped. Focus on:
46
+ - Existing patterns the builder must follow for consistency
47
+ - Known gotchas or constraints in the affected code
48
+ - Any risks introduced by the proposed change (breaking changes, perf, security)
49
+ - Whether the task touches dependencies — if so, note any implications
50
+
51
+ ### 3. Write advisory
52
+
53
+ ```
54
+ actions.start(taskId, 'consultant') → save actionId
55
+ actions.write(actionId, 'result', '<your structured advisory>')
56
+ ```
57
+
58
+ Structure your advisory with clear headings:
59
+ - **Patterns to follow** — what existing conventions apply
60
+ - **Risks & warnings** — what could go wrong
61
+ - **Best practices** — what the builder should keep in mind
62
+ - **Dependency notes** — only if task touches package.json or deps
63
+
64
+ ### 4. Complete
65
+
66
+ ```
67
+ actions.complete(actionId, 'Advisory written — <one-line summary>')
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Hard rules
73
+
74
+ - **No file writes, no edits, no Bash that changes state.** Read only.
75
+ - **Do not summarize or paraphrase** the explorer's output for the builder — add new insight.
76
+ - **Be specific.** Vague advice like "be careful" is useless. Name the file, line, pattern.
77
+ - **One action per session.** Open one action, write your advisory, close it.
@@ -91,6 +91,13 @@ Then call `permissions.check` — if `in_sync: false`, inform the user before pr
91
91
  > "Your agent permissions are outdated. Run `ahk build --sync` to update, or I can guide you."
92
92
  Wait for the user to acknowledge before continuing the session.
93
93
 
94
+ Then run deps tracking:
95
+ ```
96
+ deps.snapshot → save current dependency state (creates .harness/deps-lock.json if missing)
97
+ deps.check → returns diff vs. last snapshot
98
+ ```
99
+ Save the `deps.check` result — you'll use it in step 7 to decide whether to invoke the consultant.
100
+
94
101
  Then check session state via MCP:
95
102
 
96
103
  ```
@@ -139,6 +146,9 @@ Think through:
139
146
  - What are the acceptance criteria the reviewer will check?
140
147
  - If codebase changes are involved: does the builder need to update README or `docs/` files?
141
148
  - Does this task touch user-facing behavior (CLI commands, MCP tools, DB schema, config, agent permissions)? If yes, add an acceptance criterion: `README.md and/or docs/ updated to reflect the change`
149
+ - **Always append, as the LAST acceptance criterion for every task, this mandatory criterion:**
150
+ > `Docs/README analysis: [describe whether docs/, README.md, or other documentation files need to reflect this change and what specifically — or explicitly state 'no update needed' with brief reasoning]`
151
+ The analysis is non-negotiable. The conclusion can be "no update needed" but the reasoning must be stated. The reviewer will block if this criterion is absent or if the builder's action summary is silent on docs.
142
152
 
143
153
  Record it:
144
154
 
@@ -156,13 +166,20 @@ actions.complete(actionId, 'Plan defined — delegating to explorer')
156
166
 
157
167
  ### 7. Delegate in order
158
168
 
159
- Invoke: **Explorer** → **Builder** → **Reviewer**
169
+ Invoke: **Explorer** → **Consultant** (conditional) → **Builder** → **Reviewer**
160
170
 
161
171
  After each agent completes, read their output:
162
172
  ```
163
173
  actions.get(taskId) → read the latest completed action and its sections
164
174
  ```
165
175
 
176
+ **Invoke the Consultant when ANY of these are true:**
177
+ - `deps.check` returned `significant: true`
178
+ - `.harness/deps-lock.json` did not exist before this session (first task)
179
+ - The task description mentions `package.json`, dependencies, or config files
180
+
181
+ **Skip the Consultant** for routine feature/bug tasks where deps are unchanged.
182
+
166
183
  ### 8. Handle a Reviewer block
167
184
 
168
185
  If the reviewer blocks the task:
@@ -125,6 +125,7 @@ Then notify lead so the builder can be re-assigned.
125
125
  - **Be specific when blocking.** The builder must know exactly what to fix.
126
126
  - **Do not fix issues yourself.** Your job is to verify, not to implement.
127
127
  - **Do not approve under time pressure.** If the work is not ready, block it.
128
+ - **Verify the mandatory docs/README analysis criterion.** Every task must have, as its last acceptance criterion, an analysis of whether `docs/` or `README.md` need updating. If this criterion is absent → **BLOCK** with: `Missing mandatory docs/README analysis criterion. Lead must add it before builder proceeds.` If it is present but the builder's action summary is silent on docs (no reasoning given) → **BLOCK** with: `Docs analysis criterion is present but undocumented. Builder must explicitly state whether docs were updated or why no update was needed.`
128
129
 
129
130
  ## What counts as a block
130
131
 
@@ -135,6 +136,7 @@ Then notify lead so the builder can be re-assigned.
135
136
  - Files modified outside the builder's allowed paths
136
137
  - Security issues introduced by the changes
137
138
  - The implementation does not match the lead's plan
139
+ - Mandatory docs/README analysis criterion absent from the task, or present but not addressed in the builder's action summary
138
140
 
139
141
  ## Anti-patterns to avoid
140
142
 
package/dist/cli.js CHANGED
@@ -131,12 +131,24 @@ var MCP_CLAUDE_PERMISSIONS_REVIEWER = [
131
131
  "mcp__agent-harness-kit__tasks_acceptance_get",
132
132
  "mcp__agent-harness-kit__docs_search"
133
133
  ];
134
+ var MCP_CLAUDE_PERMISSIONS_CONSULTANT = [
135
+ "mcp__agent-harness-kit__actions_start",
136
+ "mcp__agent-harness-kit__actions_write",
137
+ "mcp__agent-harness-kit__actions_complete",
138
+ "mcp__agent-harness-kit__actions_get",
139
+ "mcp__agent-harness-kit__actions_record_file",
140
+ "mcp__agent-harness-kit__actions_record_tool",
141
+ "mcp__agent-harness-kit__tasks_get",
142
+ "mcp__agent-harness-kit__deps_snapshot",
143
+ "mcp__agent-harness-kit__deps_check"
144
+ ];
134
145
  var MCP_CLAUDE_PERMISSIONS = [
135
146
  .../* @__PURE__ */ new Set([
136
147
  ...MCP_CLAUDE_PERMISSIONS_LEAD,
137
148
  ...MCP_CLAUDE_PERMISSIONS_EXPLORER,
138
149
  ...MCP_CLAUDE_PERMISSIONS_BUILDER,
139
- ...MCP_CLAUDE_PERMISSIONS_REVIEWER
150
+ ...MCP_CLAUDE_PERMISSIONS_REVIEWER,
151
+ ...MCP_CLAUDE_PERMISSIONS_CONSULTANT
140
152
  ])
141
153
  ];
142
154
  function mergeClaudeSettingsLocalJson(filePath) {
@@ -491,6 +503,9 @@ function agentExplorer(vars) {
491
503
  function agentBuilder(vars) {
492
504
  return loadAgentTemplate("builder", vars);
493
505
  }
506
+ function agentConsultant(vars) {
507
+ return loadAgentTemplate("consultant", vars);
508
+ }
494
509
  function agentReviewer(vars) {
495
510
  return loadAgentTemplate("reviewer", vars);
496
511
  }
@@ -548,10 +563,11 @@ function agentReviewerToml(vars) {
548
563
  }
549
564
  function translateFrontmatterForClaudeCode(md, agentName) {
550
565
  const permissionsMap = {
551
- lead: MCP_CLAUDE_PERMISSIONS_LEAD,
552
- explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
553
- builder: MCP_CLAUDE_PERMISSIONS_BUILDER,
554
- reviewer: MCP_CLAUDE_PERMISSIONS_REVIEWER
566
+ lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
567
+ explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
568
+ consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
569
+ builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
570
+ reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
555
571
  };
556
572
  const permissions = permissionsMap[agentName] ?? MCP_CLAUDE_PERMISSIONS;
557
573
  const mcpLines = permissions.map((t) => ` - ${t}`).join("\n");
@@ -590,10 +606,11 @@ function slugify(title) {
590
606
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
591
607
  }
592
608
  var AGENT_TOOLS = {
593
- lead: MCP_CLAUDE_PERMISSIONS_LEAD,
594
- explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
595
- builder: MCP_CLAUDE_PERMISSIONS_BUILDER,
596
- reviewer: MCP_CLAUDE_PERMISSIONS_REVIEWER
609
+ lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
610
+ explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
611
+ consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
612
+ builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
613
+ reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
597
614
  };
598
615
  async function syncAgentPermissions(cwd2) {
599
616
  for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
@@ -645,6 +662,7 @@ No tasks in progress.
645
662
  const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
646
663
  writeAgentFile(cwd2, ".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead"));
647
664
  writeAgentFile(cwd2, ".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer"));
665
+ writeAgentFile(cwd2, ".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant"));
648
666
  writeAgentFile(cwd2, ".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder"));
649
667
  writeAgentFile(cwd2, ".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer"));
650
668
  mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
@@ -665,6 +683,7 @@ No tasks in progress.
665
683
  const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
666
684
  writeAgentFile(cwd2, ".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead"));
667
685
  writeAgentFile(cwd2, ".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer"));
686
+ writeAgentFile(cwd2, ".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant"));
668
687
  writeAgentFile(cwd2, ".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder"));
669
688
  writeAgentFile(cwd2, ".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer"));
670
689
  mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
@@ -2353,7 +2372,7 @@ async function runReset(cwd2, opts) {
2353
2372
  }
2354
2373
 
2355
2374
  // src/core/mcp-server.ts
2356
- import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync } from "fs";
2375
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync10 } from "fs";
2357
2376
  import { join as join15, resolve as resolve10 } from "path";
2358
2377
  import { Server } from "@modelcontextprotocol/sdk/server";
2359
2378
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -2366,10 +2385,11 @@ import {
2366
2385
  import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
2367
2386
  import { join as join14 } from "path";
2368
2387
  var CANONICAL = {
2369
- lead: MCP_CLAUDE_PERMISSIONS_LEAD,
2370
- explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
2371
- builder: MCP_CLAUDE_PERMISSIONS_BUILDER,
2372
- reviewer: MCP_CLAUDE_PERMISSIONS_REVIEWER
2388
+ lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
2389
+ explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
2390
+ consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
2391
+ builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
2392
+ reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
2373
2393
  };
2374
2394
  function parseToolsFromFrontmatter(content) {
2375
2395
  const match = content.match(/^---\n([\s\S]*?)\n---/m);
@@ -2382,7 +2402,7 @@ function parseToolsFromFrontmatter(content) {
2382
2402
  function checkPermissionsSync(cwd2) {
2383
2403
  const agents = {};
2384
2404
  let in_sync = true;
2385
- for (const agent of ["lead", "explorer", "builder", "reviewer"]) {
2405
+ for (const agent of ["lead", "explorer", "consultant", "builder", "reviewer"]) {
2386
2406
  const filePath = join14(cwd2, ".claude", "agents", `${agent}.md`);
2387
2407
  if (!existsSync10(filePath)) {
2388
2408
  const missing2 = CANONICAL[agent];
@@ -2631,6 +2651,16 @@ var TOOLS = [
2631
2651
  name: "permissions.check",
2632
2652
  description: "Check whether the .claude/agents/*.md tool permission lists are in sync with the current canonical permission constants. Returns per-agent diff with missing and extra tools. Call this at session start to detect outdated agent files after an ahk upgrade.",
2633
2653
  inputSchema: { type: "object", properties: {}, required: [] }
2654
+ },
2655
+ {
2656
+ name: "deps.snapshot",
2657
+ description: "Snapshot current package.json dependencies to .harness/deps-lock.json",
2658
+ inputSchema: { type: "object", properties: {}, required: [] }
2659
+ },
2660
+ {
2661
+ name: "deps.check",
2662
+ description: "Compare current package.json against .harness/deps-lock.json and report changes",
2663
+ inputSchema: { type: "object", properties: {}, required: [] }
2634
2664
  }
2635
2665
  ];
2636
2666
  async function startMcpServer(config, cwd2) {
@@ -2777,6 +2807,58 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2777
2807
  const result = checkPermissionsSync(cwd2);
2778
2808
  return ok(JSON.stringify(result, null, 2));
2779
2809
  }
2810
+ case "deps.snapshot": {
2811
+ const pkgPath2 = join15(cwd2, "package.json");
2812
+ if (!existsSync11(pkgPath2)) {
2813
+ return ok("package.json not found in project root", true);
2814
+ }
2815
+ const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
2816
+ const snapshot = {
2817
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
2818
+ dependencies: pkg2.dependencies ?? {},
2819
+ devDependencies: pkg2.devDependencies ?? {}
2820
+ };
2821
+ const harnessDir = join15(cwd2, ".harness");
2822
+ mkdirSync9(harnessDir, { recursive: true });
2823
+ writeFileSync10(join15(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
2824
+ return ok(JSON.stringify({ message: "Snapshot saved to .harness/deps-lock.json", capturedAt: snapshot.capturedAt }));
2825
+ }
2826
+ case "deps.check": {
2827
+ const pkgPath2 = join15(cwd2, "package.json");
2828
+ const lockPath = join15(cwd2, ".harness", "deps-lock.json");
2829
+ if (!existsSync11(pkgPath2)) {
2830
+ return ok("package.json not found in project root", true);
2831
+ }
2832
+ if (!existsSync11(lockPath)) {
2833
+ return ok(JSON.stringify({ status: "no-snapshot", message: "No deps-lock.json found. Run deps.snapshot first to establish a baseline." }));
2834
+ }
2835
+ const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
2836
+ const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
2837
+ const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
2838
+ const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
2839
+ const added = [];
2840
+ const removed = [];
2841
+ const majorBumps = [];
2842
+ for (const [name2, version] of Object.entries(current)) {
2843
+ if (!(name2 in previous)) {
2844
+ added.push(`${name2}@${version}`);
2845
+ } else {
2846
+ const prevMajor = parseInt(previous[name2].replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
2847
+ const curMajor = parseInt(version.replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
2848
+ if (!isNaN(prevMajor) && !isNaN(curMajor) && curMajor > prevMajor) {
2849
+ majorBumps.push({ name: name2, from: previous[name2], to: version });
2850
+ }
2851
+ }
2852
+ }
2853
+ for (const depName of Object.keys(previous)) {
2854
+ if (!(depName in current)) {
2855
+ removed.push(depName);
2856
+ }
2857
+ }
2858
+ const significant = added.length > 0 || removed.length > 0 || majorBumps.length > 0;
2859
+ const advisory = significant ? "Significant dependency changes detected. Consider running `pnpx autoskills` (or `npx autoskills` if pnpm is unavailable) to refresh agent skills. Clearing stale skills before re-running is recommended." : "No significant dependency changes detected.";
2860
+ return ok(JSON.stringify({ significant, added, removed, majorBumps, advisory, snapshotDate: lock.capturedAt }));
2861
+ }
2780
2862
  default:
2781
2863
  return ok(`Unknown tool: ${name}`, true);
2782
2864
  }
@@ -2934,7 +3016,7 @@ async function runStatus(cwd2, opts) {
2934
3016
  }
2935
3017
 
2936
3018
  // src/commands/sync.ts
2937
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
3019
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
2938
3020
  import { join as join16, resolve as resolve11 } from "path";
2939
3021
  import pc10 from "picocolors";
2940
3022
  async function runSync(cwd2, opts) {
@@ -2954,7 +3036,7 @@ async function runSync(cwd2, opts) {
2954
3036
  }
2955
3037
  }
2956
3038
  async function syncIn(featureListPath, db, dryRun) {
2957
- if (!existsSync11(featureListPath)) {
3039
+ if (!existsSync12(featureListPath)) {
2958
3040
  console.log(pc10.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
2959
3041
  return;
2960
3042
  }
@@ -3045,14 +3127,14 @@ async function runTaskAdd(cwd2) {
3045
3127
 
3046
3128
  // src/commands/task/done.ts
3047
3129
  import { spawnSync as spawnSync2 } from "child_process";
3048
- import { existsSync as existsSync12 } from "fs";
3130
+ import { existsSync as existsSync13 } from "fs";
3049
3131
  import { resolve as resolve12 } from "path";
3050
3132
  import pc12 from "picocolors";
3051
3133
  async function runTaskDone(cwd2, idOrSlug) {
3052
3134
  const config = await loadConfig(cwd2);
3053
3135
  if (config.health.required) {
3054
3136
  const scriptPath = resolve12(cwd2, config.health.scriptPath);
3055
- if (existsSync12(scriptPath)) {
3137
+ if (existsSync13(scriptPath)) {
3056
3138
  const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
3057
3139
  if (result.status !== 0) {
3058
3140
  console.error(pc12.red("\u2717 Health check failed \u2014 cannot mark task as done."));