@jaybeeuu/agent-cortex 1.39.0 → 1.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.agents/skills/style-versioning/SKILL.md +1 -1
  2. package/.github/workflows/ci.yml +47 -14
  3. package/AGENTS.md +35 -8
  4. package/CHANGELOG.md +60 -0
  5. package/README.md +174 -97
  6. package/agents/README.md +14 -11
  7. package/bin/agent-cortex.mjs +55 -10
  8. package/bin/installers/claude.mjs +674 -174
  9. package/bin/installers/copilot.mjs +13 -18
  10. package/bin/installers/pi.mjs +293 -51
  11. package/claude/settings.json +4 -0
  12. package/{claude → claude-extras}/scripts/statusline-command.sh +32 -4
  13. package/docs/claude-hooks.md +15 -3
  14. package/docs/ideas/ban-content-assertion-tests.md +73 -0
  15. package/docs/ideas/bead-progress-notes-for-session-resumption.md +75 -0
  16. package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +70 -0
  17. package/docs/ideas/ctx-alias-version-and-update-commands.md +65 -0
  18. package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +67 -0
  19. package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +36 -0
  20. package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +99 -0
  21. package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +33 -0
  22. package/docs/ideas/extension-manifests.md +55 -14
  23. package/docs/ideas/improve-pi-system-prompt.md +5 -2
  24. package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +95 -0
  25. package/docs/ideas/record-idea-implementation-questions.md +68 -0
  26. package/docs/ideas/self-flushing-session.md +72 -0
  27. package/docs/ideas/state-in-beads-not-md.md +69 -0
  28. package/docs/ideas/task-session-context.local.md +95 -0
  29. package/docs/ideas/test-data-creation-helpers.md +92 -0
  30. package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +68 -0
  31. package/docs/ideas/worktree-housekeeping-for-closed-beads.md +39 -0
  32. package/docs/technical-direction/harness-config.md +161 -0
  33. package/docs/technical-direction/migration-execution-labels.md +60 -23
  34. package/extensions/agent-guardrails/README.md +45 -0
  35. package/extensions/agent-guardrails/guardrails.test.ts +42 -0
  36. package/extensions/agent-guardrails/guardrails.ts +66 -0
  37. package/extensions/agent-guardrails/index.ts +22 -0
  38. package/extensions/agent-guardrails/node_modules/.bin/pi +21 -0
  39. package/extensions/agent-guardrails/node_modules/.bin/tsc +21 -0
  40. package/extensions/agent-guardrails/node_modules/.bin/tsserver +21 -0
  41. package/extensions/agent-guardrails/node_modules/.bin/tsx +21 -0
  42. package/extensions/agent-guardrails/package.json +19 -0
  43. package/extensions/agent-guardrails/tsconfig.json +12 -0
  44. package/extensions/agent-modes/README.md +12 -5
  45. package/extensions/agent-modes/discover.test.ts +30 -7
  46. package/extensions/agent-modes/discover.ts +1 -1
  47. package/extensions/agent-modes/index.test.ts +208 -0
  48. package/extensions/agent-modes/index.ts +23 -6
  49. package/extensions/agent-modes/package.json +1 -1
  50. package/extensions/skill-stats/index.test.ts +3 -2
  51. package/lib/cli.mjs +30 -12
  52. package/lib/pi-packages.mjs +169 -0
  53. package/package.json +8 -4
  54. package/pi/settings.json +23 -12
  55. package/plugin.json +1 -1
  56. package/scripts/build-copilot-agents.mjs +1 -1
  57. package/scripts/check-no-sync.mjs +49 -0
  58. package/scripts/lib/compose-agent.mjs +32 -14
  59. package/scripts/lib/fs.mjs +31 -0
  60. package/skills/engineering/grill-with-docs/SKILL.md +1 -1
  61. package/skills/planning/classify-bead/REFERENCE.md +85 -0
  62. package/skills/planning/classify-bead/SKILL.md +32 -36
  63. package/skills/planning/classify-bead/scripts/classify-bead.mjs +292 -0
  64. package/skills/planning/create-task/SKILL.md +19 -10
  65. package/skills/planning/write-a-prd/SKILL.md +2 -2
  66. package/skills/style/style-code/REFERENCE.md +32 -2
  67. package/skills/style/style-code/SKILL.md +5 -21
  68. package/skills/style/style-tests/SKILL.md +69 -17
  69. package/skills/workflow/hitl-collab/SKILL.md +1 -1
  70. package/skills/workflow/run-pipeline-stage/SKILL.md +13 -1
  71. package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +12 -0
  72. package/test/classify-bead.test.mjs +277 -0
  73. package/test/claude-installer.test.mjs +199 -139
  74. package/test/cli.test.mjs +95 -41
  75. package/test/copilot-installer.test.mjs +92 -82
  76. package/test/fs.test.mjs +76 -0
  77. package/test/helpers/fake-claude.mjs +152 -0
  78. package/test/installer.test.mjs +542 -57
  79. package/test/pi-installer.test.mjs +457 -102
  80. package/test/pi-packages.test.mjs +295 -0
  81. package/token-map.README.md +23 -12
  82. package/token-map.json +8 -7
  83. package/.claude-plugin/marketplace.json +0 -16
  84. package/claude/.claude-plugin/plugin.json +0 -11
  85. package/claude/agents/plan.md +0 -58
  86. package/claude/agents/ralph-plan.md +0 -121
  87. package/claude/agents/ralph.md +0 -193
  88. package/claude/agents/strategy.md +0 -111
  89. package/claude/hooks/scripts/notify.mjs +0 -54
  90. package/claude/hooks.json +0 -38
  91. /package/{claude → claude-extras}/.mcp.json +0 -0
package/pi/settings.json CHANGED
@@ -4,26 +4,37 @@
4
4
  "maxNameLength": 40,
5
5
  "maxSummaryLength": 100
6
6
  },
7
- "lastChangelogVersion": "0.83.0",
7
+ "lastChangelogVersion": "0.87.1",
8
8
  "packages": [
9
- "../../src/agent-cortex",
9
+ {
10
+ "source": "../../src/agent-cortex",
11
+ "skills": []
12
+ },
10
13
  "npm:pi-web-access@0.10.7",
11
14
  "npm:@getpipher/vision",
12
15
  "npm:@hypabolic/pi-hypa",
13
- "npm:context-mode"
16
+ "npm:context-mode",
17
+ "npm:pi-questions"
14
18
  ],
15
19
  "defaultProvider": "opencode-go",
16
- "defaultModel": "qwen3.7-max",
20
+ "defaultModel": "deepseek-v4.1-flash",
17
21
  "theme": "dark",
18
22
  "defaultThinkingLevel": "high",
19
23
  "enabledModels": [
20
- "opencode/nemotron-3-ultra-free",
21
- "opencode-go/qwen3.7-max",
22
- "opencode/kimi-k2.7-code",
23
- "opencode/mimo-v2.5-free",
24
- "opencode-go/mimo-v2.5",
25
- "opencode/deepseek-v4-flash-free",
24
+ "opencode/big-pickle",
25
+ "opencode-go/qwen3.7-plus",
26
26
  "opencode-go/deepseek-v4-flash",
27
- "opencode/big-pickle"
28
- ]
27
+ "opencode-go/kimi-k3",
28
+ "opencode/deepseek-v4-flash",
29
+ "opencode/mimo-v2.5-free",
30
+ "opencode-go/deepseek-v4.1-flash"
31
+ ],
32
+ "compaction": {
33
+ "enabled": true
34
+ },
35
+ "steeringMode": "all",
36
+ "followUpMode": "all",
37
+ "tuiMode": "regular",
38
+ "treeFilterMode": "default",
39
+ "hideThinkingBlock": true
29
40
  }
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-cortex",
3
3
  "description": "Personal Copilot CLI plugin with custom agents and skills",
4
- "version": "1.39.0",
4
+ "version": "1.41.0",
5
5
  "author": {
6
6
  "name": "jaybeeuu"
7
7
  },
@@ -27,4 +27,4 @@ import { installCopilot } from "../bin/installers/copilot.mjs";
27
27
 
28
28
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
29
29
 
30
- installCopilot({ root: ROOT, output: join(ROOT, "agents") });
30
+ await installCopilot({ root: ROOT, output: join(ROOT, "agents") });
@@ -0,0 +1,49 @@
1
+ // Repo convention: never block the main thread — no `*Sync` functions
2
+ // (fs/child_process/...) in tooling code. Runs via `pnpm lint` (CI) and
3
+ // locally. Scans the toolchain and test dirs: bin/, scripts/, lib/, hooks/,
4
+ // test/. Skill-owned scripts (skills/*/scripts) and extensions are separate
5
+ // follow-up surfaces tracked outside this check.
6
+ import { readdir, readFile } from "node:fs/promises";
7
+ import { join, relative } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
11
+ const SCAN_DIRS = ["bin", "scripts", "lib", "hooks", "test"];
12
+ const EXTENSIONS = new Set([".mjs", ".js", ".ts"]);
13
+ const SELF = "scripts/check-no-sync.mjs";
14
+
15
+ const SYNC_CALL = /\b\w*Sync\s*\(/;
16
+
17
+ /** Recursively collect files under dir, skipping this script and node_modules. */
18
+ async function collect(dir) {
19
+ const out = [];
20
+ const entries = await readdir(dir, { withFileTypes: true });
21
+ for (const e of entries) {
22
+ if (e.name === "node_modules") continue;
23
+ const full = join(dir, e.name);
24
+ if (e.isDirectory()) out.push(...(await collect(full)));
25
+ else if (EXTENSIONS.has(e.name.slice(e.name.lastIndexOf(".")))) out.push(full);
26
+ }
27
+ return out;
28
+ }
29
+
30
+ const offenders = [];
31
+ for (const dir of SCAN_DIRS) {
32
+ const abs = join(ROOT, dir);
33
+ for (const file of await collect(abs)) {
34
+ const rel = relative(ROOT, file);
35
+ if (rel === SELF) continue;
36
+ const src = await readFile(file, "utf-8");
37
+ const hits = src.split("\n").map((line, i) => ({ line: i + 1, text: line })).filter((l) => SYNC_CALL.test(l.text));
38
+ if (hits.length) offenders.push({ rel, hits });
39
+ }
40
+ }
41
+
42
+ if (offenders.length) {
43
+ console.error("✗ Never block the main thread — *Sync function calls found:");
44
+ for (const { rel, hits } of offenders) {
45
+ for (const { line, text } of hits) console.error(` ${rel}:${line}: ${text.trim()}`);
46
+ }
47
+ process.exit(1);
48
+ }
49
+ console.log("✓ no *Sync calls in tooling code (bin/, scripts/, lib/, hooks/, test/)");
@@ -29,9 +29,19 @@
29
29
  //
30
30
  // Zero dependencies so it runs on the CI Node (20) and local Node alike.
31
31
 
32
- import { readFileSync, existsSync } from "node:fs";
32
+ import { readFile, stat } from "node:fs/promises";
33
33
  import { join } from "node:path";
34
34
 
35
+ /** True when a path exists (stat succeeds). */
36
+ async function pathExists(p) {
37
+ try {
38
+ await stat(p);
39
+ return true;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
35
45
  // ─── Token substitution ──────────────────────────────────────────────────────
36
46
 
37
47
  const TOOL_RE = /\{\{TOOL:([^}]+)\}\}/g;
@@ -92,22 +102,30 @@ export function substituteTokens(content, harness, tokenMap, options = {}) {
92
102
  * @param {string} harness Harness id: "copilot" | "claude" | "pi"
93
103
  * @returns {{ name: string, description: string, tools: string[], model?: string, argumentHint?: string, body: string }}
94
104
  */
95
- export function composeAgent(root, name, harness, options = {}) {
105
+ export async function composeAgent(root, name, harness, options = {}) {
96
106
  const { dropNullTools = false, pluginRoot, resolveRelativePaths = false, warn = noop } = options;
97
107
  const agentDir = join(root, "agents", name);
98
- const frontmatter = parseFrontmatter(join(agentDir, harness, "frontmatter.json"), name, harness);
99
- const tokenMap = loadTokenMap(root);
108
+ const frontmatter = await parseFrontmatter(join(agentDir, harness, "frontmatter.json"), name, harness);
109
+ const tokenMap = await loadTokenMap(root);
100
110
 
101
- let body = readFileSync(join(agentDir, "agent.md"), "utf-8");
111
+ let body = await readFile(join(agentDir, "agent.md"), "utf-8");
102
112
 
103
- // 1. {{SECTION:name}} → <harness>/<name>.md (may itself contain TOOL/PATH tokens)
104
- body = body.replace(/\{\{SECTION:([^}]+)\}\}/g, (_token, section) => {
113
+ // 1. {{SECTION:name}} → <harness>/<name>.md (may itself contain TOOL/PATH
114
+ // tokens). Single pass over the original body — inserted section content
115
+ // is never rescanned (same semantics as the sync replace()).
116
+ const sectionRe = /\{\{SECTION:([^}]+)\}\}/g;
117
+ let out = "";
118
+ let last = 0;
119
+ for (let m = sectionRe.exec(body); m; m = sectionRe.exec(body)) {
120
+ const section = m[1];
105
121
  const sectionPath = join(agentDir, harness, `${section}.md`);
106
- if (!existsSync(sectionPath)) {
122
+ if (!(await pathExists(sectionPath))) {
107
123
  throw new Error(`agent "${name}": missing section "${section}" for harness "${harness}" (${sectionPath})`);
108
124
  }
109
- return readFileSync(sectionPath, "utf-8").trim();
110
- });
125
+ out += body.slice(last, m.index) + (await readFile(sectionPath, "utf-8")).trim();
126
+ last = m.index + m[0].length;
127
+ }
128
+ body = out + body.slice(last);
111
129
 
112
130
  // 2. + 3. TOOL/PATH substitution (contextualised for error messages)
113
131
  body = substituteTokens(body, harness, tokenMap, {
@@ -127,8 +145,8 @@ const REQUIRED_FIELDS = ["name", "description", "tools"];
127
145
  const KNOWN_OPTIONAL_FIELDS = ["model", "argumentHint"];
128
146
 
129
147
  /** Parse and schema-check a harness frontmatter.json (strict, per agents/README.md). */
130
- function parseFrontmatter(path, name, harness) {
131
- const raw = readFileSync(path, "utf-8");
148
+ async function parseFrontmatter(path, name, harness) {
149
+ const raw = await readFile(path, "utf-8");
132
150
  let fm;
133
151
  try {
134
152
  fm = JSON.parse(raw);
@@ -225,11 +243,11 @@ export function translateToolList(tools, tokenMap, harness, { warn = noop } = {}
225
243
  // ─── Token map ───────────────────────────────────────────────────────────────
226
244
 
227
245
  /** Load token-map.json from the package root; missing/incomplete maps hard-fail. */
228
- export function loadTokenMap(root) {
246
+ export async function loadTokenMap(root) {
229
247
  const path = join(root, "token-map.json");
230
248
  let raw;
231
249
  try {
232
- raw = readFileSync(path, "utf-8");
250
+ raw = await readFile(path, "utf-8");
233
251
  } catch {
234
252
  throw new Error(`missing ${path} — the composer requires token-map.json`);
235
253
  }
@@ -0,0 +1,31 @@
1
+ // Shared async filesystem predicates. The install-time generators all need the
2
+ // same "does this path exist and is it a file/directory?" probe, and the repo
3
+ // convention is async-only (no *Sync) in tooling code — see scripts/check-no-sync.mjs.
4
+ //
5
+ // Both probes are stat-based and resolve `false` on any error (missing path,
6
+ // permission denied, broken symlink), so callers can branch on existence
7
+ // without a separate try/catch:
8
+ //
9
+ // if (await isFile(join(dir, "agent.md"))) { … }
10
+ //
11
+ // Zero dependencies so it runs on the CI Node (20) and local Node alike.
12
+
13
+ import { stat } from "node:fs/promises";
14
+
15
+ /** True when `p` exists and is a regular file; false on any error. */
16
+ export async function isFile(p) {
17
+ try {
18
+ return (await stat(p)).isFile();
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ /** True when `p` exists and is a directory; false on any error. */
25
+ export async function isDirectory(p) {
26
+ try {
27
+ return (await stat(p)).isDirectory();
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: grill-with-docs
3
- description: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when the user wants to stress-test a plan against their project's language and documented decisions: "grill my plan", "check this against our docs".
3
+ description: "Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when the user wants to stress-test a plan against their project's language and documented decisions: \"grill my plan\", \"check this against our docs\"."
4
4
  ---
5
5
 
6
6
  # Grill With Docs
@@ -0,0 +1,85 @@
1
+ # classify-bead reference
2
+
3
+ Detail behind `SKILL.md`. The executable `scripts/classify-bead.mjs` is the source of
4
+ truth for the deterministic tiers; this document mirrors it for verification.
5
+
6
+ ## Tier precedence
7
+
8
+ | Tier | `source` | Evidence | Applies the label? |
9
+ |---|---|---|---|
10
+ | 1 | `label` | An `implementation-type:afk` or `implementation-type:hitl` label is already present | no — it is already there |
11
+ | 2 | `type-field` | The legacy `## Type` section in the bead body declares `AFK`/`HITL` (block or inline form, case-insensitive) | yes |
12
+ | 3 | `heuristic` | A conservative natural-language signal (below) | yes |
13
+ | 4 | *(none)* | Nothing deterministic matched → `escalate: true`; a small/cheap model applies the rubric | after the model decides |
14
+
15
+ When both `implementation-type` labels are present, AFK wins — matching the
16
+ pre-existing skill wording, which checks `implementation-type:afk` first.
17
+
18
+ The classifier runs `bd show <id> --json` and `bd tag <id> <label>`; it never
19
+ re-reads prose from the rendered `bd show` output.
20
+
21
+ ## Heuristic signals
22
+
23
+ Heuristics only fire on explicit phrasing. They are checked in this order,
24
+ mirroring the rubric's "a human is required if any criterion is true" rule.
25
+
26
+ HITL (human required):
27
+
28
+ - `requires human`, `human sign-off|approval|judgement|decision`
29
+ - `manual step|action|intervention`
30
+ - `visually review|inspect|verify`
31
+ - `stakeholder sign-off|approval`
32
+ - `credentials|secrets setup|rotation|provisioning`
33
+ - `provisioning` within 40 characters of `infrastructure|infra|server|cluster`
34
+
35
+ AFK (explicitly no human):
36
+
37
+ - `no human action|input|review|intervention|judgement`
38
+
39
+ AFK (machine-checkable acceptance):
40
+
41
+ - `machine-checkable`
42
+ - `automated test|tests|check|checks|verification`
43
+
44
+ Anything else escalates. Widening this list trades model calls for
45
+ misclassification risk; only add phrasing that maps directly onto a rubric
46
+ criterion.
47
+
48
+ Matching is literal, not semantic, and the patterns are not negation-aware —
49
+ "no human approval is required" still matches the HITL tier. The error direction
50
+ is toward HITL, which is visible and correctable, so prefer unambiguous phrasing
51
+ over more patterns.
52
+
53
+ ## CLI
54
+
55
+ ```bash
56
+ node classify-bead.mjs <bead-id> [--dry-run] # resolve (and tag) one bead
57
+ node classify-bead.mjs --audit [--limit <n>] # measure the split over recent beads
58
+ ```
59
+
60
+ Run from the target project's directory — `bd` resolves its beads DB from cwd.
61
+ JSON goes to stdout:
62
+
63
+ - Classification: `{ id, classification, source, reason, escalate, applied, label }`
64
+ - Audit: `{ sampled, bySource, deterministic, needsRubric, deterministicRatio }`
65
+
66
+ Exit codes: `0` success (including `escalate`, which is a valid outcome), `2`
67
+ usage error, `1` `bd` failure. `BD_PATH` overrides the `bd` executable.
68
+
69
+ ## Measured split
70
+
71
+ This is the "measure first" number that sizes the win over spawning a subagent.
72
+ Reproduce it against the repo's beads DB:
73
+
74
+ ```bash
75
+ node <skill-scripts>/classify-bead.mjs --audit --limit 100
76
+ ```
77
+
78
+ Measured 2026-09-18 on `agent-cortex`: 78 of the newest 100 beads resolved
79
+ deterministically (77 from an existing label, 1 from a heuristic) and 22 needed
80
+ the rubric. That is the share of classifications that skip the model entirely
81
+ when the caller runs the classifier before spawning a subagent. The sample is
82
+ the newest 100 beads, so the ratio moves as beads are created — re-run the
83
+ command rather than quoting this snapshot. The deterministic rate is higher on
84
+ backlogs labelled at planning time; freshly created beads with neither a label
85
+ nor a `## Type` field lean on the heuristic tier.
@@ -5,7 +5,7 @@ description: Classify a bead as AFK or HITL and apply the implementation-type la
5
5
 
6
6
  # Classify Bead
7
7
 
8
- Determine whether a bead requires human action (HITL) or can be completed autonomously by an agent (AFK), then persist the result as an `implementation-type` label.
8
+ Determine whether a bead requires human action (HITL) or can be completed autonomously by an agent (AFK), then persist the result as an `implementation-type` label. A deterministic classifier settles the common cases; only genuine rubric judgement reaches a model.
9
9
 
10
10
  ## When to use
11
11
 
@@ -21,71 +21,67 @@ Determine whether a bead requires human action (HITL) or can be completed autono
21
21
 
22
22
  ## Invocation
23
23
 
24
- Run this skill as a background subagent via {{TOOL:task}} so bead content never loads into the calling agent's context. Classification is a rubric lookup, not code generation — a small, cheap model is sufficient. Pass the bead ID and the `bd prime` output; the subagent runs the workflow below and reports back the classification plus the `bd tag` command it applied.
24
+ Run the deterministic classifier first. It resolves the existing label, the legacy `## Type` field, and explicit heuristic signals without a model call:
25
25
 
26
- Only run inline when no task-spawning capability exists in the current environment.
26
+ ```bash
27
+ # Run from the TARGET project's directory — bd resolves its beads DB from cwd.
28
+ node <skill-scripts>/classify-bead.mjs <bead-id>
29
+ ```
27
30
 
28
- **Callers should check `bd label list <id>` themselves first.** If `implementation-type` is already present, skip spawning this skill entirely — spawning a subagent purely to re-read a label that already exists wastes a full agent call.
31
+ - Output carries `"classification": "afk" | "hitl"` → done. The classifier applied the label; report the classification. Do not spawn a subagent.
32
+ - Output carries `"escalate": true` → no deterministic signal. Spawn this skill as a background subagent via {{TOOL:task}} (a small, cheap model) with the bead ID and the `bd prime` output; the subagent applies the rubric below and tags the bead.
33
+ - `--dry-run` resolves without applying the label. `--audit [--limit <n>]` reports the deterministic-vs-rubric split over recent beads.
29
34
 
30
- ## Workflow
31
-
32
- 1. **Check for an existing label.**
33
-
34
- ```bash
35
- bd label list <id>
36
- ```
35
+ `<skill-scripts>` is the absolute path to this skill's own `scripts/` directory, resolved from wherever this skill was loaded — it varies by harness and install location, so never hardcode one harness's path. Only run inline when no task-spawning capability exists in the current environment.
37
36
 
38
- - `implementation-type:afk` present → classification is **AFK**. Stop here.
39
- - `implementation-type:hitl` present → classification is **HITL**. Stop here.
37
+ **Callers should run the classifier themselves before spawning this subagent.** If `implementation-type` is already present, or the classifier resolves the bead, there is no subagent call to make.
40
38
 
41
- 2. **Check the `## Type` field (back-compat).**
39
+ ## Workflow
42
40
 
43
- ```bash
44
- bd show <id>
45
- ```
41
+ 1. **Check for an existing label.** `bd label list <id>` — `implementation-type:afk` present → **AFK**; `implementation-type:hitl` present → **HITL**. Stop.
46
42
 
47
- Locate the `## Type` section in the bead body. `HITL` or `AFK` (case-insensitive) determines the classification; continue to step 4. This field predates the label system, so treat it as authoritative when present.
43
+ 2. **Run the deterministic classifier.** `node <skill-scripts>/classify-bead.mjs <id>`. A resolved classification is already tagged; stop and report it.
48
44
 
49
- 3. **Classify from first principles** — only when neither the label nor the `## Type` field is present. Read the full bead body and apply the rubric:
45
+ 3. **Apply the rubric** — only when the classifier reports `escalate: true`. Read the full bead body:
50
46
 
51
- **HITL** — the bead requires a human if at least one of the following is true:
47
+ **HITL** — a human is required if at least one of the following holds:
52
48
  - The outcome cannot be verified by an agent (e.g. visual review, stakeholder sign-off, UX judgement call).
53
49
  - A manual action only a human can perform is required (e.g. credential setup, secrets management, external service configuration, infrastructure provisioning outside the codebase).
54
50
  - A decision must be made that the agent cannot make unilaterally (e.g. architectural choice between equally valid options, regulatory or legal sign-off).
55
51
 
56
- **AFK** — the agent can implement, verify, and complete the task autonomously, and all acceptance criteria are machine-checkable.
57
-
58
- 4. **Apply the label.**
52
+ **AFK** — otherwise: the agent can implement, verify, and complete the task autonomously, and all acceptance criteria are machine-checkable.
59
53
 
60
- ```bash
61
- bd tag <id> implementation-type:afk # or implementation-type:hitl
62
- ```
54
+ 4. **Apply the label.** `bd tag <id> implementation-type:afk` (or `:hitl`). The classifier does this for the deterministic tiers; do it yourself only after step 3.
63
55
 
64
56
  5. **Return the classification.** Report `AFK` or `HITL` so the calling skill or agent can act on it.
65
57
 
58
+ Tier precedence (label → `## Type` → heuristics → rubric), the heuristic signal list, the CLI contract, and the measured deterministic split are in `REFERENCE.md`.
59
+
66
60
  ## Red Flags
67
61
 
68
- - Classifying a bead without the caller-side `bd label list` check — you either overwrite a recorded decision or waste an agent spawn re-reading a label that already exists.
62
+ - Classifying a bead without the caller-side `bd label list` check — you either overwrite a recorded decision or waste a call re-reading a label that already exists.
63
+ - Spawning the rubric subagent when the classifier returned a classification — the deterministic tier is the point.
69
64
  - Defaulting to HITL for a complex task whose acceptance criteria are machine-checkable — complexity alone is not a criterion.
70
- - Choosing a class without reading the bead body when neither the label nor `## Type` is present.
65
+ - Choosing a class without reading the bead body when the classifier escalated.
71
66
 
72
67
  ## Common Rationalizations
73
68
 
74
69
  | Rationalization | Rebuttal |
75
- |---|---|
76
70
  | "This task is complex, so it must be HITL" | Complexity is not a criterion. HITL applies only when a human is genuinely required to complete or verify. |
77
71
  | "I'll mark it HITL to be safe" | HITL consumes scarce human attention. Prefer AFK — ralph's review gates still catch agent mistakes. |
78
- | "The label is probably already there, I'll skip the check" | One `bd label list` command settles it. Skipping it either overwrites a decision or spawns an agent to re-read a label. |
72
+ | "The label is probably already there, I'll skip the check" | One `bd label list` command settles it, and the classifier checks it too. |
73
+ | "Heuristics are good enough for this one" | The classifier uses conservative, explicit signals only. If it escalated, read the body. |
79
74
 
80
75
  ## Philosophy / rationale
81
76
 
82
- - Classification gates automation against human attention: AFK beads feed ralph's autonomous pipeline, HITL beads are routed to a person. We prefer AFK because human time is the scarce resource, and later review gates still catch agent mistakes — escalate only when completion or verification genuinely requires a human.
83
- - The rubric is deterministic, so this skill is a lookup rather than deep reasoning — which is why a cheap subagent suffices and the step 1 early exit exists.
77
+ - Classification gates automation against human attention: AFK beads feed ralph's autonomous pipeline, HITL beads route to a person. Prefer AFK because human time is the scarce resource, and later review gates still catch agent mistakes.
78
+ - The rubric is deterministic, so most classifications are a lookup. The executable encodes that lookup once, and the model is reserved for beads whose evidence is genuinely ambiguous.
84
79
 
85
80
  ## Verification checklist
86
81
 
87
82
  - [ ] `bd label list <id>` was checked before any other step.
88
- - [ ] Classification followed the precedence order: existing label → `## Type` → first-principles rubric.
89
- - [ ] The full bead body was read before classifying from first principles (step 3).
90
- - [ ] `bd tag <id> implementation-type:<afk|hitl>` was applied.
91
- - [ ] The calling skill or agent received the classification in the report.
83
+ - [ ] The deterministic classifier ran before any rubric subagent.
84
+ - [ ] A rubric subagent ran only when the classifier reported `escalate: true`.
85
+ - [ ] The full bead body was read before classifying from first principles.
86
+ - [ ] `bd tag <id> implementation-type:<afk|hitl>` is present afterwards.
87
+ - [ ] The calling skill or agent received the classification in the report.