@jaybeeuu/agent-cortex 2.0.0 → 2.2.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 (183) hide show
  1. package/CHANGELOG.md +437 -0
  2. package/README.md +46 -15
  3. package/agents/README.md +22 -10
  4. package/agents/plan/agent.md +2 -2
  5. package/agents/ralph/agent.md +128 -91
  6. package/agents/ralph/claude/frontmatter.json +3 -3
  7. package/agents/ralph/claude/wake.md +46 -0
  8. package/agents/ralph/pi/{polling.md → wake.md} +35 -20
  9. package/agents/ralph-plan/agent.md +23 -11
  10. package/bin/agent-cortex.mjs +66 -4
  11. package/bin/installers/claude.mjs +262 -77
  12. package/bin/installers/pi.mjs +200 -24
  13. package/extensions/agent-guardrails/README.md +1 -1
  14. package/extensions/agent-guardrails/guardrails.ts +1 -1
  15. package/extensions/agent-guardrails/index.ts +1 -1
  16. package/extensions/agent-modes/discover.test.ts +2 -2
  17. package/extensions/output-style/README.md +58 -0
  18. package/extensions/output-style/index.ts +23 -0
  19. package/extensions/output-style/output-style.md +43 -0
  20. package/extensions/output-style/output-style.test.ts +50 -0
  21. package/extensions/output-style/output-style.ts +32 -0
  22. package/extensions/output-style/package.json +19 -0
  23. package/hooks/claude/scripts/notify.mjs +8 -1
  24. package/lib/cli.mjs +91 -2
  25. package/package.json +29 -4
  26. package/scripts/check-changeset.mjs +4 -0
  27. package/scripts/check-no-sync.mjs +5 -1
  28. package/scripts/check-skill-frontmatter.mjs +21 -0
  29. package/scripts/lib/compose-agent.mjs +69 -9
  30. package/scripts/lib/fs.mjs +10 -2
  31. package/scripts/lib/migrate.mjs +736 -0
  32. package/scripts/lib/release-gates.mjs +21 -1
  33. package/scripts/lib/skill-frontmatter.mjs +152 -0
  34. package/scripts/lib/skill-links.mjs +52 -0
  35. package/scripts/lib/verify-worker-tree.mjs +25 -0
  36. package/scripts/resolve-publish-command.mjs +4 -0
  37. package/skills/engineering/design-an-interface/SKILL.md +3 -3
  38. package/skills/engineering/grill-with-docs/SKILL.md +3 -5
  39. package/skills/engineering/improve-codebase-architecture/SKILL.md +5 -4
  40. package/skills/engineering/tdd/SKILL.md +5 -7
  41. package/skills/engineering/technical-direction/SKILL.md +5 -5
  42. package/skills/planning/classify-bead/REFERENCE.md +3 -3
  43. package/skills/planning/classify-bead/SKILL.md +14 -9
  44. package/skills/planning/classify-bead/scripts/classify-bead.mjs +5 -4
  45. package/skills/planning/create-task/REFERENCE.md +52 -3
  46. package/skills/planning/create-task/SKILL.md +70 -26
  47. package/skills/planning/create-task/scripts/{create-chores.ts → create-chores.mjs} +48 -23
  48. package/skills/planning/flesh-out-idea/REFERENCE.md +116 -0
  49. package/skills/planning/flesh-out-idea/SKILL.md +146 -0
  50. package/skills/planning/prd-to-tasks/REFERENCE.md +54 -5
  51. package/skills/planning/prd-to-tasks/SKILL.md +22 -16
  52. package/skills/planning/record-idea/FORMAT.md +66 -17
  53. package/skills/planning/record-idea/SKILL.md +48 -24
  54. package/skills/planning/request-refactor-plan/SKILL.md +4 -4
  55. package/skills/planning/spec-to-tickets/SKILL.md +5 -5
  56. package/skills/planning/write-a-prd/SKILL.md +8 -9
  57. package/skills/planning/write-a-ticket/SKILL.md +4 -6
  58. package/skills/productivity/bd-tool/SKILL.md +15 -10
  59. package/skills/productivity/grill-me/SKILL.md +5 -0
  60. package/skills/productivity/init-beads/SKILL.md +3 -3
  61. package/skills/productivity/migration/REFERENCE.md +227 -0
  62. package/skills/productivity/migration/SKILL.md +188 -0
  63. package/skills/productivity/validate-agent-dir/scripts/{validate-agent-dir.ts → validate-agent-dir.mjs} +49 -28
  64. package/skills/productivity/write-a-skill/SKILL.md +5 -5
  65. package/skills/review/maintain-agent-docs/SKILL.md +5 -7
  66. package/skills/review/refactor-skill/SKILL.md +4 -3
  67. package/skills/review/review-security/SKILL.md +3 -5
  68. package/skills/style/style-code/SKILL.md +5 -10
  69. package/skills/style/style-comms/SKILL.md +5 -7
  70. package/skills/style/style-documentation/SKILL.md +5 -6
  71. package/skills/style/style-tests/SKILL.md +5 -7
  72. package/skills/workflow/git-workflow/SKILL.md +28 -4
  73. package/skills/workflow/hitl-collab/SKILL.md +4 -6
  74. package/skills/workflow/plan/SKILL.md +41 -30
  75. package/skills/workflow/ralph/REFERENCE.md +209 -55
  76. package/skills/workflow/ralph/SKILL.md +89 -50
  77. package/skills/workflow/ralph/scripts/pending-gates.mjs +134 -0
  78. package/skills/workflow/run-pipeline-stage/SKILL.md +72 -71
  79. package/skills/workflow/run-pipeline-stage/playbooks/code.md +11 -0
  80. package/skills/workflow/run-pipeline-stage/playbooks/document.md +12 -0
  81. package/skills/workflow/run-pipeline-stage/playbooks/fix.md +12 -0
  82. package/skills/workflow/run-pipeline-stage/playbooks/review.md +11 -0
  83. package/skills/workflow/run-pipeline-stage/playbooks/verify.md +13 -0
  84. package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +73 -1
  85. package/token-map.README.md +9 -4
  86. package/token-map.json +13 -6
  87. package/.agents/skills/style-versioning/SKILL.md +0 -172
  88. package/.changeset/README.md +0 -8
  89. package/.changeset/config.json +0 -11
  90. package/.github/workflows/ci.yml +0 -302
  91. package/.junie/guidelines.md +0 -87
  92. package/.junie/mcp/mcp.json +0 -10
  93. package/AGENTS.md +0 -151
  94. package/agents/ralph/claude/polling.md +0 -31
  95. package/agents-native/ralph.md +0 -193
  96. package/docs/claude-hooks.md +0 -128
  97. package/docs/commit-signing.md +0 -231
  98. package/docs/ideas/auto-model-selection.md +0 -75
  99. package/docs/ideas/ban-content-assertion-tests.md +0 -75
  100. package/docs/ideas/bead-progress-notes-for-session-resumption.md +0 -75
  101. package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +0 -70
  102. package/docs/ideas/ctx-alias-version-and-update-commands.md +0 -65
  103. package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +0 -67
  104. package/docs/ideas/dedicated-review-tests-skill.md +0 -71
  105. package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +0 -36
  106. package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +0 -99
  107. package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +0 -42
  108. package/docs/ideas/extension-manifests.md +0 -121
  109. package/docs/ideas/improve-pi-system-prompt.md +0 -37
  110. package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +0 -95
  111. package/docs/ideas/model-selector.md +0 -40
  112. package/docs/ideas/pi-task-memory.md +0 -34
  113. package/docs/ideas/qa-extension.md +0 -43
  114. package/docs/ideas/record-idea-implementation-questions.md +0 -68
  115. package/docs/ideas/record-ideas-as-beads.md +0 -115
  116. package/docs/ideas/self-flushing-session.md +0 -72
  117. package/docs/ideas/state-in-beads-not-md.md +0 -69
  118. package/docs/ideas/subagent-visibility.md +0 -72
  119. package/docs/ideas/task-session-context.local.md +0 -95
  120. package/docs/ideas/task-session-context.md +0 -89
  121. package/docs/ideas/test-data-creation-helpers.md +0 -92
  122. package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +0 -68
  123. package/docs/ideas/unified-ci-pipeline.md +0 -28
  124. package/docs/ideas/unify-the-ticket-and-task-decomposition-workflows.md +0 -71
  125. package/docs/ideas/worktree-housekeeping-for-closed-beads.md +0 -39
  126. package/docs/inspirations.md +0 -33
  127. package/docs/research/disable-model-invocation.md +0 -82
  128. package/docs/research/gas-city-research.md +0 -256
  129. package/docs/research/skill-design-research.md +0 -286
  130. package/docs/research/skill-improvements-analysis.md +0 -117
  131. package/docs/research/skill-patterns-research.md +0 -24
  132. package/docs/skills/skill-anatomy.md +0 -197
  133. package/docs/technical-direction/harness-config.md +0 -177
  134. package/docs/technical-direction/migration-execution-labels.md +0 -177
  135. package/docs/user-preferences.md +0 -17
  136. package/extensions/agent-guardrails/tsconfig.json +0 -12
  137. package/extensions/agent-modes/tsconfig.json +0 -12
  138. package/extensions/session-start/tsconfig.json +0 -12
  139. package/extensions/skill-stats/pnpm-lock.yaml +0 -329
  140. package/extensions/skill-stats/tsconfig.json +0 -12
  141. package/extensions/subagent/agents/planner.md +0 -37
  142. package/extensions/subagent/agents/reviewer.md +0 -35
  143. package/extensions/subagent/agents/scout.md +0 -50
  144. package/extensions/subagent/agents/worker.md +0 -24
  145. package/extensions/subagent/prompts/implement-and-review.md +0 -10
  146. package/extensions/subagent/prompts/implement.md +0 -10
  147. package/extensions/subagent/prompts/scout-and-plan.md +0 -9
  148. package/extensions/subagent/tsconfig.json +0 -12
  149. package/pi/keybindings.json +0 -3
  150. package/pi/settings.json +0 -34
  151. package/pnpm-workspace.yaml +0 -14
  152. package/skills/planning/create-task/scripts/package.json +0 -14
  153. package/skills/planning/create-task/scripts/pnpm-lock.yaml +0 -342
  154. package/skills/planning/create-task/scripts/tsconfig.json +0 -12
  155. package/skills/planning/record-idea/scripts/new-idea.sh +0 -89
  156. package/skills/productivity/validate-agent-dir/scripts/package.json +0 -15
  157. package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +0 -329
  158. package/skills/productivity/validate-agent-dir/scripts/pnpm-workspace.yaml +0 -2
  159. package/skills/productivity/validate-agent-dir/scripts/tsconfig.json +0 -19
  160. package/skills/productivity/validate-agent-dir/scripts/validate-agent-dir.test.ts +0 -317
  161. package/skills/workflow/run-pipeline-stage/REFERENCE.md +0 -22
  162. package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +0 -682
  163. package/skills/workflow/run-pipeline-stage/scripts/generate-progress.ts +0 -588
  164. package/skills/workflow/run-pipeline-stage/scripts/package.json +0 -15
  165. package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +0 -342
  166. package/skills/workflow/run-pipeline-stage/scripts/pnpm-workspace.yaml +0 -2
  167. package/skills/workflow/run-pipeline-stage/scripts/tsconfig.json +0 -12
  168. package/test/classify-bead.test.mjs +0 -277
  169. package/test/claude-installer.test.mjs +0 -308
  170. package/test/cli.test.mjs +0 -314
  171. package/test/create-chores.test.mjs +0 -85
  172. package/test/fake-bd.test.mjs +0 -72
  173. package/test/fs.test.mjs +0 -76
  174. package/test/helpers/fake-bd.mjs +0 -87
  175. package/test/helpers/fake-claude.mjs +0 -152
  176. package/test/helpers/fake-pi.mjs +0 -132
  177. package/test/installer.test.mjs +0 -664
  178. package/test/notify-hook.test.mjs +0 -63
  179. package/test/pi-installer.test.mjs +0 -785
  180. package/test/pi-register.test.mjs +0 -342
  181. package/test/pi-sweep.test.mjs +0 -619
  182. package/test/release-gates.test.mjs +0 -190
  183. package/test/verify-worker-tree.test.mjs +0 -120
@@ -16,9 +16,21 @@ const GITHUB_ACTIONS_BOT = "github-actions[bot]";
16
16
  const CHANGESET_PATH = /^\.changeset\/[^/]+\.md$/;
17
17
  const CHANGESET_EXEMPT_PATHS = new Set([".changeset/README.md"]);
18
18
 
19
+ /** @typedef {{ status: string, path: string }} NameStatusEntry */
20
+
21
+ /**
22
+ * @typedef {object} ChangesetGateInput
23
+ * @property {NameStatusEntry[]} changes
24
+ * @property {string[]} labels
25
+ * @property {string} headRef
26
+ * @property {string} actor
27
+ */
28
+
19
29
  /**
20
30
  * Parse `git diff --name-status` output into `{ status, path }` entries, where
21
31
  * `status` is the single-letter change type and `path` is the new path.
32
+ * @param {string} output
33
+ * @returns {NameStatusEntry[]}
22
34
  */
23
35
  export function parseNameStatus(output) {
24
36
  return output
@@ -26,7 +38,7 @@ export function parseNameStatus(output) {
26
38
  .filter((line) => line.trim() !== "")
27
39
  .map((line) => {
28
40
  const [status, ...paths] = line.split("\t");
29
- return { status: status.charAt(0), path: paths.at(-1) };
41
+ return { status: status.charAt(0), path: /** @type {string} */ (paths.at(-1)) };
30
42
  });
31
43
  }
32
44
 
@@ -34,6 +46,8 @@ export function parseNameStatus(output) {
34
46
  * Decide whether a pull request may skip adding a changeset. Exempt only the
35
47
  * Version Packages PR and PRs explicitly labelled `no-publish`; every other PR
36
48
  * must add its own changeset.
49
+ * @param {ChangesetGateInput} input
50
+ * @returns {{ ok: boolean, reason: string }}
37
51
  */
38
52
  export function evaluateChangesetGate({ changes, labels, headRef, actor }) {
39
53
  if (headRef.startsWith(VERSION_PACKAGES_HEAD_REF) || actor === GITHUB_ACTIONS_BOT) {
@@ -51,11 +65,17 @@ export function evaluateChangesetGate({ changes, labels, headRef, actor }) {
51
65
  /**
52
66
  * Resolve the command changesets/action should exec for publishing, based on
53
67
  * the labels of the PR merged for the pushed commit.
68
+ * @param {string[]} labels
69
+ * @returns {string}
54
70
  */
55
71
  export function resolvePublishCommand(labels) {
56
72
  return labels.includes(NO_PUBLISH_LABEL) ? NO_PUBLISH_COMMAND : PUBLISH_COMMAND;
57
73
  }
58
74
 
75
+ /**
76
+ * @param {string} path
77
+ * @returns {boolean}
78
+ */
59
79
  function isChangeset(path) {
60
80
  return CHANGESET_PATH.test(path) && !CHANGESET_EXEMPT_PATHS.has(path);
61
81
  }
@@ -0,0 +1,152 @@
1
+ // SKILL.md frontmatter validation, shared by `pnpm lint` (via
2
+ // scripts/check-skill-frontmatter.mjs) and its tests.
3
+ //
4
+ // PI reads a skill's frontmatter with `parse` from the `yaml` package and
5
+ // silently drops the skill when that parse throws — only a warning diagnostic
6
+ // is emitted. A skill whose frontmatter is broken therefore disappears from the
7
+ // prompt with no visible failure. This module makes that failure loud: every
8
+ // SKILL.md must parse, declare a non-empty `name` and `description`, and keep
9
+ // the description within PI's 1024-character limit.
10
+ //
11
+ // Frontmatter extraction mirrors PI's parseFrontmatter (dist/utils/frontmatter.js), so a
12
+ // SKILL.md that PI drops — frontmatter that will not parse, or no description — fails here
13
+ // too. The check is stricter than PI in two places PI only warns about: `name` must be a
14
+ // non-empty frontmatter field (PI falls back to the directory name), and a description over
15
+ // 1024 characters is an error.
16
+ //
17
+ // Known gaps, each verified against PI's loader and left unhandled on purpose:
18
+ // - A symlinked SKILL.md is skipped: `entry.isFile()` is false for a symlink, while PI
19
+ // stats through the link and loads it. No symlinked SKILL.md exists in this repo.
20
+ // - Duplicate `name` values across skill roots are not detected: validation is per-file
21
+ // with no name index, while PI drops the loser of a name collision.
22
+ import { readdir, readFile } from "node:fs/promises";
23
+ import { join, relative } from "node:path";
24
+ import { parse } from "yaml";
25
+
26
+ /** PI's maximum description length — a longer description is rejected. */
27
+ export const MAX_DESCRIPTION_LENGTH = 1024;
28
+
29
+ const SKILL_FILE = "SKILL.md";
30
+ const SKIPPED_DIRS = new Set([".git", "node_modules"]);
31
+
32
+ /**
33
+ * Return the YAML text between the leading `---` delimiters, or null when the
34
+ * document has no frontmatter block. Mirrors PI's extraction step, including
35
+ * newline normalisation and BOM stripping.
36
+ * @param {string} content
37
+ * @returns {string | null}
38
+ */
39
+ function extractYaml(content) {
40
+ const normalized = content.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
41
+ if (!normalized.startsWith("---")) return null;
42
+
43
+ const endIndex = normalized.indexOf("\n---", 3);
44
+ if (endIndex === -1) return null;
45
+
46
+ return normalized.slice(4, endIndex);
47
+ }
48
+
49
+ /**
50
+ * True when `value` is a string with at least one non-whitespace character.
51
+ * `yaml`'s `parse` is typed `any`, so the frontmatter values it yields are
52
+ * taken as `unknown` here and narrowed by this typeof guard.
53
+ * @param {unknown} value
54
+ * @returns {value is string}
55
+ */
56
+ function isNonEmptyString(value) {
57
+ return typeof value === "string" && value.trim() !== "";
58
+ }
59
+
60
+ /**
61
+ * Validate one SKILL.md document.
62
+ *
63
+ * @param {string} content - Raw SKILL.md contents.
64
+ * @returns {string[]} Human-readable problems; empty when the frontmatter is valid.
65
+ */
66
+ export function validateSkillFrontmatter(content) {
67
+ const yamlText = extractYaml(content);
68
+ if (yamlText === null) {
69
+ return ["frontmatter block is missing (expected '---' delimiters)"];
70
+ }
71
+
72
+ /** @type {unknown} */
73
+ let frontmatter;
74
+ try {
75
+ frontmatter = parse(yamlText);
76
+ } catch (error) {
77
+ const message = error instanceof Error ? error.message.split("\n")[0] : "unknown parse error";
78
+ return [`frontmatter is not valid YAML: ${message}`];
79
+ }
80
+
81
+ if (frontmatter === null || typeof frontmatter !== "object" || Array.isArray(frontmatter)) {
82
+ return ["frontmatter is not a YAML mapping of name/description fields"];
83
+ }
84
+
85
+ // The guard above establishes a non-null, non-array object, so the YAML
86
+ // mapping's fields are readable as unknown values.
87
+ const fields = /** @type {Record<string, unknown>} */ (frontmatter);
88
+ const errors = [];
89
+ if (!isNonEmptyString(fields.name)) {
90
+ errors.push("frontmatter is missing a non-empty name");
91
+ }
92
+ if (!isNonEmptyString(fields.description)) {
93
+ errors.push("frontmatter is missing a non-empty description");
94
+ } else if (fields.description.length > MAX_DESCRIPTION_LENGTH) {
95
+ errors.push(
96
+ `description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${fields.description.length})`,
97
+ );
98
+ }
99
+
100
+ return errors;
101
+ }
102
+
103
+ /**
104
+ * Recursively collect every SKILL.md below `dir`, skipping vendored trees.
105
+ * @param {string} dir
106
+ * @returns {Promise<string[]>}
107
+ */
108
+ async function findSkillFiles(dir) {
109
+ /** @type {string[]} */
110
+ const found = [];
111
+ let entries;
112
+ try {
113
+ entries = await readdir(dir, { withFileTypes: true });
114
+ } catch {
115
+ return found;
116
+ }
117
+
118
+ for (const entry of entries) {
119
+ const full = join(dir, entry.name);
120
+ if (entry.isDirectory()) {
121
+ if (SKIPPED_DIRS.has(entry.name)) continue;
122
+ found.push(...(await findSkillFiles(full)));
123
+ } else if (entry.isFile() && entry.name === SKILL_FILE) {
124
+ found.push(full);
125
+ }
126
+ }
127
+
128
+ return found;
129
+ }
130
+
131
+ /**
132
+ * Validate every SKILL.md under `root`.
133
+ *
134
+ * @param {{ root: string }} options
135
+ * @returns {Promise<{ checked: number, failures: Array<{ file: string, errors: string[] }> }>}
136
+ * `checked` counts the SKILL.md files examined; `failures` holds one entry per
137
+ * invalid file, with `file` relative to `root`.
138
+ */
139
+ export async function checkSkills({ root }) {
140
+ const files = await findSkillFiles(root);
141
+ const failures = [];
142
+
143
+ for (const file of files) {
144
+ const content = await readFile(file, "utf-8");
145
+ const errors = validateSkillFrontmatter(content);
146
+ if (errors.length > 0) {
147
+ failures.push({ file: relative(root, file), errors });
148
+ }
149
+ }
150
+
151
+ return { checked: files.length, failures };
152
+ }
@@ -0,0 +1,52 @@
1
+ // Flat-layout link rewriting for installed skills.
2
+ //
3
+ // Skills are authored in a grouped tree (skills/<group>/<name>/), so a handoff to
4
+ // a neighbouring skill is written against that tree — ../../style/style-code/SKILL.md.
5
+ // The pi installer preserves the grouping, so those paths resolve as written. The
6
+ // Claude Code plugin cannot: its scan expects skills/<name>/SKILL.md directly under
7
+ // skills/, so bin/installers/claude.mjs copies every skill flat and the authored path
8
+ // loses its group segment. Rewriting the link at copy time keeps one authored source
9
+ // that is correct in the repo and correct in both install layouts.
10
+ //
11
+ // Zero dependencies so it runs on the CI Node (20) and local Node alike.
12
+ //
13
+ // Known limits. The rewrite keys off the first path segment that matches a skill name,
14
+ // so it cannot tell a skill directory from a same-named directory elsewhere: a target
15
+ // like ../../../docs/plan/x.md (plan is a skill name) becomes ../plan/x.md, pointing at
16
+ // a file that does not exist in the flattened install. An angle-bracket target such as
17
+ // <plan> loses its brackets and gains a ../ for the same reason. Neither shape exists in
18
+ // the corpus today — links only ever enter skills/ trees with plain relative targets —
19
+ // so this is a constraint on new links, not a live bug. A non-md file that links back
20
+ // into a skill tree has the same caveat: only markdown under a skill directory is
21
+ // transformed.
22
+
23
+ const LINK_RE = /\]\(([^)\s]+)\)/g;
24
+ const NON_RELATIVE_RE = /^(?:[a-z][a-z0-9+.-]*:|\/|#)/i;
25
+
26
+ /**
27
+ * Rewrite relative markdown links that traverse a skill directory into flat sibling
28
+ * references, so they resolve after the grouped tree is flattened.
29
+ *
30
+ * `../../style/style-code/SKILL.md` → `../style-code/SKILL.md`. Links that do not
31
+ * traverse a skill directory (`./EXAMPLES.md`, `../tdd/tests.md`) are left alone, as
32
+ * are schemes, absolute paths, and anchors.
33
+ *
34
+ * @param {string} content Markdown file content
35
+ * @param {Set<string> | string[]} skillNames Every skill name in the install
36
+ * @returns {string}
37
+ */
38
+ export function flattenSkillLinks(content, skillNames) {
39
+ const names = skillNames instanceof Set ? skillNames : new Set(skillNames);
40
+ /** @param {string} match @param {string} target @returns {string} */
41
+ return content.replace(LINK_RE, (match, target) => {
42
+ if (NON_RELATIVE_RE.test(target)) return match;
43
+ const segments = target.split("/");
44
+ /** @param {string} segment @returns {boolean} */
45
+ const isSkill = (segment) => names.has(segment);
46
+ const skillAt = segments.findIndex(isSkill);
47
+ // First path segment: a same-directory link, already correct when flattened.
48
+ if (skillAt <= 0) return match;
49
+ const rest = segments.slice(skillAt + 1);
50
+ return `](../${[segments[skillAt], ...rest].join("/")})`;
51
+ });
52
+ }
@@ -26,11 +26,20 @@ import { fileURLToPath } from "node:url";
26
26
  const execFileAsync = promisify(execFile);
27
27
  const SELF = fileURLToPath(import.meta.url);
28
28
 
29
+ /**
30
+ * @param {string} worktree
31
+ * @param {string[]} args
32
+ * @returns {Promise<string>}
33
+ */
29
34
  async function git(worktree, args) {
30
35
  const { stdout } = await execFileAsync("git", args, { cwd: worktree });
31
36
  return stdout.trim();
32
37
  }
33
38
 
39
+ /**
40
+ * @param {string} porcelain
41
+ * @returns {number}
42
+ */
34
43
  function countPaths(porcelain) {
35
44
  return porcelain.split("\n").filter((line) => line.trim() !== "").length;
36
45
  }
@@ -39,6 +48,8 @@ function countPaths(porcelain) {
39
48
  * Check that a worker delivered: clean worktree and at least one commit ahead
40
49
  * of `base`. Returns the reasons it failed, so the caller can surface them
41
50
  * instead of guessing why a stage did not advance.
51
+ * @param {{ worktree: string, base: string }} options
52
+ * @returns {Promise<{ ok: boolean, reasons: string[], commitsAhead: number }>}
42
53
  */
43
54
  export async function verifyWorkerTree({ worktree, base }) {
44
55
  const reasons = [];
@@ -58,7 +69,12 @@ export async function verifyWorkerTree({ worktree, base }) {
58
69
 
59
70
  // ─── CLI ─────────────────────────────────────────────────────────────────────
60
71
 
72
+ /**
73
+ * @param {string[]} argv
74
+ * @returns {Record<string, string | true>}
75
+ */
61
76
  function parseFlags(argv) {
77
+ /** @type {Record<string, string | true>} */
62
78
  const flags = {};
63
79
  for (let i = 0; i < argv.length; i++) {
64
80
  const arg = argv[i];
@@ -75,6 +91,11 @@ function parseFlags(argv) {
75
91
  return flags;
76
92
  }
77
93
 
94
+ /**
95
+ * @param {Record<string, string | true>} flags
96
+ * @param {string} name
97
+ * @returns {string}
98
+ */
78
99
  function required(flags, name) {
79
100
  const value = flags[name];
80
101
  if (typeof value !== "string" || value === "") {
@@ -84,6 +105,10 @@ function required(flags, name) {
84
105
  return value;
85
106
  }
86
107
 
108
+ /**
109
+ * @param {string[]} argv
110
+ * @returns {Promise<void>}
111
+ */
87
112
  async function main(argv) {
88
113
  const flags = parseFlags(argv);
89
114
  const verdict = await verifyWorkerTree({
@@ -4,6 +4,10 @@
4
4
  // stay a plain `pnpm <script>` token pair.
5
5
  import { resolvePublishCommand } from "./lib/release-gates.mjs";
6
6
 
7
+ /**
8
+ * @param {string} raw
9
+ * @returns {string[]}
10
+ */
7
11
  function readLabels(raw) {
8
12
  try {
9
13
  return JSON.parse(raw);
@@ -93,10 +93,10 @@ description: "Generate multiple radically different interface designs for a modu
93
93
  - [ ] Phase 4 complete: comparison shared and divergence highlighted
94
94
  - [ ] Phase 5 complete: candidate synthesized with user input
95
95
 
96
- ## Cross-skill references
96
+ ## See Also
97
97
 
98
- - `improve-codebase-architecture` invokes this skill for its multi-design step (step 5 of its workflow).
99
- - Run `grill-with-docs` when the module's domain terminology is unsettled — design names should match the ubiquitous language.
98
+ - [improve-codebase-architecture](../improve-codebase-architecture/SKILL.md) — runs this skill for its multi-design step; load it when the designs feed a deepening decision.
99
+ - [grill-with-docs](../grill-with-docs/SKILL.md) — load when the module's domain terminology is unsettled, so design names match the ubiquitous language.
100
100
 
101
101
  ## Examples
102
102
 
@@ -53,12 +53,10 @@ description: "Grilling session that challenges your plan against the existing do
53
53
  - **`CONTEXT.md` is a glossary and nothing else** — the vocabulary stays stable while the design churns around it.
54
54
  - **ADRs are rare** — the three criteria keep the ADR log a history of decisions, not a diary.
55
55
 
56
- ## Cross-skill references
56
+ ## See Also
57
57
 
58
- | When you need… | Use |
59
- |---|---|
60
- | A design interview without touching project docs | `grill-me` |
61
- | A justified recommendation on architecture or technology | `technical-direction` |
58
+ - [grill-me](../../productivity/grill-me/SKILL.md) — the same interview without the doc updates, when no CONTEXT.md or ADR work is wanted.
59
+ - [technical-direction](../technical-direction/SKILL.md) — continues into a justified recommendation once the terminology has settled.
62
60
 
63
61
  ## Examples
64
62
 
@@ -76,11 +76,12 @@ A **deep module** (John Ousterhout, "A Philosophy of Software Design") has a sma
76
76
  - [ ] Phase 3 complete: at least two radically different designs presented and compared, with a recommendation given
77
77
  - [ ] Phase 4 complete: refactor bead created via `create-task` and the bead ID reported
78
78
 
79
- ## Cross-skill references
79
+ ## See Also
80
80
 
81
- - Run `design-an-interface` at step 5 for a guided parallel sub-agent design flow.
82
- - Run `create-task` at step 7 to file the refactor bead.
83
- - Run `grill-with-docs` when the deepened module's terminology is unsettled — interface names should match the ubiquitous language.
81
+ - [design-an-interface](../design-an-interface/SKILL.md) — step 5 hands the deep-module redesign to its parallel design flow.
82
+ - [create-task](../../planning/create-task/SKILL.md) — step 7 files the refactor as a bead; never call `bd create` directly.
83
+ - [grill-with-docs](../grill-with-docs/SKILL.md) — load when the deepened module's terminology is unsettled.
84
+ - [style-code](../../style/style-code/SKILL.md) — load when the deepened module needs the naming and boundary conventions it will ship with.
84
85
 
85
86
  ## Examples
86
87
 
@@ -91,15 +91,13 @@ After all tests pass, run `style-code` to guide the refactoring shape:
91
91
  | "This is too simple for TDD" | The simplest changes often have the most hidden assumptions. TDD surfaces them early. |
92
92
  | "I already know what the implementation looks like" | Then write the test first to prove it. If the test forces a different interface, the implementation was wrong. |
93
93
 
94
- ## Cross-skill references
94
+ ## See Also
95
95
 
96
- | When you need… | Use this skill |
97
- |---|---|
98
- | Test-writing conventions (assertions, mocking, structure) | `style-tests` |
99
- | Code-level style and conventions | `style-code` |
100
- | Domain terminology for test names and module boundaries | `grill-with-docs` |
96
+ - [style-tests](../../style/style-tests/SKILL.md) — load before writing the first test; it owns assertion strategy, mock discipline, and test-data construction.
97
+ - [style-code](../../style/style-code/SKILL.md) — load at the refactor step for naming and module boundaries.
98
+ - [grill-with-docs](../grill-with-docs/SKILL.md) — load when domain vocabulary is unsettled, so test names and seams use the project's own language.
101
99
 
102
- Reference docs bundled with this skill: `tests.md`, `mocking.md`, `deep-modules.md`, `interface-design.md`, `refactoring.md`.
100
+ Bundled reference docs: `tests.md`, `mocking.md`, `deep-modules.md`, `interface-design.md`, `refactoring.md`.
103
101
 
104
102
  ## Examples
105
103
 
@@ -54,12 +54,12 @@ Act as a pragmatic, challenging staff engineer: constructive, direct, and eviden
54
54
  - **Challenge is the deliverable.** The value of a direction session is the assumptions it surfaces and kills. Optimising for agreement produces a recommendation nobody tested.
55
55
  - **Every recommendation carries its tradeoffs.** A choice without documented tradeoffs, revisit triggers, and validation signals cannot be revisited intelligently later.
56
56
 
57
- ## Cross-skill references
57
+ ## See Also
58
58
 
59
- - `write-a-prd` produces the input brief when no PRD exists yet.
60
- - `grill-with-docs` sharpens contested terminology and stress-tests the memo against domain docs.
61
- - `design-an-interface` structures the option-generation step when the decision is about interface shape.
62
- - `style-documentation` governs the memo's what/whether criteria before writing.
59
+ - [write-a-prd](../../planning/write-a-prd/SKILL.md) — load when the decision needs a requirements brief that does not exist yet.
60
+ - [grill-with-docs](../grill-with-docs/SKILL.md) — load when the contested point is vocabulary rather than technology.
61
+ - [design-an-interface](../design-an-interface/SKILL.md) — load when the decision is interface shape and option generation needs structure.
62
+ - [style-documentation](../../style/style-documentation/SKILL.md) — load to decide whether the decision memo gets written at all.
63
63
 
64
64
  ## Examples
65
65
 
@@ -7,13 +7,13 @@ truth for the deterministic tiers; this document mirrors it for verification.
7
7
 
8
8
  | Tier | `source` | Evidence | Applies the label? |
9
9
  |---|---|---|---|
10
- | 1 | `label` | An `implementation-type:afk` or `implementation-type:hitl` label is already present | no — it is already there |
10
+ | 1 | `label` | An `execution:afk` or `execution:hitl` label is already present | no — it is already there |
11
11
  | 2 | `type-field` | The legacy `## Type` section in the bead body declares `AFK`/`HITL` (block or inline form, case-insensitive) | yes |
12
12
  | 3 | `heuristic` | A conservative natural-language signal (below) | yes |
13
13
  | 4 | *(none)* | Nothing deterministic matched → `escalate: true`; a small/cheap model applies the rubric | after the model decides |
14
14
 
15
- When both `implementation-type` labels are present, AFK wins — matching the
16
- pre-existing skill wording, which checks `implementation-type:afk` first.
15
+ When both `execution` labels are present, AFK wins — matching the
16
+ pre-existing skill wording, which checks `execution:afk` first.
17
17
 
18
18
  The classifier runs `bd show <id> --json` and `bd tag <id> <label>`; it never
19
19
  re-reads prose from the rendered `bd show` output.
@@ -1,27 +1,27 @@
1
1
  ---
2
2
  name: classify-bead
3
- description: Classify a bead as AFK or HITL and apply the implementation-type label. Use when a bead is missing its implementation-type label, when picking up a bead to work on, or when any skill creates a new bead.
3
+ description: Classify a bead as AFK or HITL and apply its `execution` label. Use when a bead is missing its execution label, when picking up a bead to work on, or when any skill creates a new bead.
4
4
  ---
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. A deterministic classifier settles the common cases; only genuine rubric judgement reaches a model.
8
+ Determine whether a bead requires human action (HITL) or can be completed autonomously by an agent (AFK), then persist the result as an `execution` label (`execution:afk` or `execution:hitl`). A deterministic classifier settles the common cases; only genuine rubric judgement reaches a model.
9
9
 
10
10
  ## When to use
11
11
 
12
- - A bead is missing its `implementation-type` label and you are about to pick it up.
12
+ - A bead is missing its `execution` label and you are about to pick it up.
13
13
  - A skill has just created a new bead and needs it classified before claiming or scheduling.
14
14
  - You are unsure of a bead's classification and want to resolve it deterministically.
15
15
 
16
16
  ## When NOT to use
17
17
 
18
- - The bead already carries an `implementation-type` label — check `bd label list <id>` before invoking, and never re-classify a labelled bead.
18
+ - The bead already carries an `execution` label — check `bd label list <id>` before invoking, and never re-classify a labelled bead.
19
19
  - You are creating a new task — `create-task` classifies internally; do not spawn this skill separately.
20
20
  - The work item is not a bead (e.g. a Jira ticket or plain code task) — use `write-a-ticket` or start the work directly.
21
21
 
22
22
  ## Invocation
23
23
 
24
- Run the deterministic classifier first. It resolves the existing label, the legacy `## Type` field, and explicit heuristic signals without a model call:
24
+ Run the deterministic classifier first. It resolves the existing `execution` label, the legacy `## Type` field, and explicit heuristic signals without a model call:
25
25
 
26
26
  ```bash
27
27
  # Run from the TARGET project's directory — bd resolves its beads DB from cwd.
@@ -34,11 +34,11 @@ node <skill-scripts>/classify-bead.mjs <bead-id>
34
34
 
35
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.
36
36
 
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.
37
+ **Callers should run the classifier themselves before spawning this subagent.** If an `execution` label is already present, or the classifier resolves the bead, there is no subagent call to make.
38
38
 
39
39
  ## Workflow
40
40
 
41
- 1. **Check for an existing label.** `bd label list <id>` — `implementation-type:afk` present → **AFK**; `implementation-type:hitl` present → **HITL**. Stop.
41
+ 1. **Check for an existing label.** `bd label list <id>` — `execution:afk` present → **AFK**; `execution:hitl` present → **HITL**. Stop.
42
42
 
43
43
  2. **Run the deterministic classifier.** `node <skill-scripts>/classify-bead.mjs <id>`. A resolved classification is already tagged; stop and report it.
44
44
 
@@ -51,7 +51,7 @@ node <skill-scripts>/classify-bead.mjs <bead-id>
51
51
 
52
52
  **AFK** — otherwise: the agent can implement, verify, and complete the task autonomously, and all acceptance criteria are machine-checkable.
53
53
 
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.
54
+ 4. **Apply the label.** `bd tag <id> execution:afk` (or `execution:hitl`). The classifier does this for the deterministic tiers; do it yourself only after step 3.
55
55
 
56
56
  5. **Return the classification.** Report `AFK` or `HITL` so the calling skill or agent can act on it.
57
57
 
@@ -77,11 +77,16 @@ Tier precedence (label → `## Type` → heuristics → rubric), the heuristic s
77
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
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.
79
79
 
80
+ ## See Also
81
+
82
+ - [create-task](../create-task/SKILL.md) — calls this classifier for every bead it creates; the two must agree on label vocabulary.
83
+ - [ralph](../../workflow/ralph/SKILL.md) — reads `bd ready -l execution:afk`; a mislabelled bead is invisible to the pipeline.
84
+
80
85
  ## Verification checklist
81
86
 
82
87
  - [ ] `bd label list <id>` was checked before any other step.
83
88
  - [ ] The deterministic classifier ran before any rubric subagent.
84
89
  - [ ] A rubric subagent ran only when the classifier reported `escalate: true`.
85
90
  - [ ] The full bead body was read before classifying from first principles.
86
- - [ ] `bd tag <id> implementation-type:<afk|hitl>` is present afterwards.
91
+ - [ ] `bd tag <id> execution:<afk|hitl>` is present afterwards.
87
92
  - [ ] The calling skill or agent received the classification in the report.
@@ -1,8 +1,9 @@
1
+ // @ts-check
1
2
  // Deterministic bead classifier — resolves an AFK/HITL classification from
2
3
  // evidence already present on a bead before any model judgement is spent.
3
4
  //
4
5
  // Tier order (see skills/planning/classify-bead/SKILL.md):
5
- // 1. implementation-type label — an existing decision is authoritative
6
+ // 1. execution label — an existing decision is authoritative
6
7
  // 2. legacy ## Type field — back-compat with pre-label beads
7
8
  // 3. conservative heuristics — explicit natural-language signals only
8
9
  // 4. escalate — hand to the rubric (small/cheap model)
@@ -24,8 +25,8 @@ import { pathToFileURL } from 'node:url';
24
25
  * @property {boolean} escalate True when model judgement is required (tier 4).
25
26
  */
26
27
 
27
- const AFK_LABEL = 'implementation-type:afk';
28
- const HITL_LABEL = 'implementation-type:hitl';
28
+ const AFK_LABEL = 'execution:afk';
29
+ const HITL_LABEL = 'execution:hitl';
29
30
 
30
31
  // Heuristic signals are deliberately conservative: they only fire on explicit
31
32
  // natural-language phrasing that maps directly onto the rubric's criteria.
@@ -94,7 +95,7 @@ function extractTypeField(description) {
94
95
  }
95
96
 
96
97
  /**
97
- * Resolve a bead's implementation type from the evidence it already carries,
98
+ * Resolve a bead's execution class from the evidence it already carries,
98
99
  * without spending a model call.
99
100
  *
100
101
  * @param {{ labels?: string[], description?: string }} bead
@@ -1,15 +1,16 @@
1
1
  # Create Task — Reference
2
2
 
3
- Detail behind `skills/planning/create-task/SKILL.md`. The script (`scripts/create-chores.ts`) is the source of truth for this contract; this document mirrors it for verification.
3
+ Detail behind `skills/planning/create-task/SKILL.md`. The script (`scripts/create-chores.mjs`) is the source of truth for this contract; this document mirrors it for verification.
4
4
 
5
5
  ## Script invocation
6
6
 
7
7
  ```
8
- tsx create-chores.ts --parent <bead-id> [--priority <0-4>]
8
+ node <skill-scripts>/create-chores.mjs --parent <bead-id> [--priority <0-4>]
9
9
  ```
10
10
 
11
11
  - Reads `<skill>/pipeline.json`; creates each stage chore via `bd create` and wires dependencies via `bd dep add`.
12
12
  - Prints a JSON object mapping each stage ID to its chore bead ID, plus `featurePrReview` for the PR gate.
13
+ - Expands a bead that already exists — it never creates a parent. It is also not idempotent: running it against a bead that already has stage chores creates a second chain, so callers check for existing `stage:*` children first (ralph's expansion guard).
13
14
 
14
15
  ## Stage chore beads
15
16
 
@@ -24,10 +25,58 @@ One per stage in `pipeline.json` (currently `code`, `verify`, `review`, `documen
24
25
 
25
26
  - **Type**: `task`
26
27
  - **Title**: `[<parent-id>] PR Review and Merge`
27
- - **Labels**: `implementation-type:hitl`, `lifecycle:feature-pr` — exactly one `implementation-type` label. The gate declares its own labels, so the script passes `--no-inherit-labels` and it never picks up the parent's `implementation-type:afk`. Stage chores do **not** opt out; they keep inheriting `:afk`.
28
+ - **Labels**: `execution:hitl`, `lifecycle:feature-pr` — exactly one `execution` label. The gate declares its own labels, so the script passes `--no-inherit-labels` and it never picks up the parent's `execution:afk`. Stage chores do **not** opt out; they keep inheriting `execution:afk` from the parent.
29
+ - **Parent contract**: the parent must carry `execution:afk`. Stage chores inherit the parent's labels, so a parent labelled `execution:hitl` (or unlabelled) produces a chore tree that no executor routes. The script does not classify — it only checks that it was given a parent ID.
28
30
  - **Dependencies**: `parent-child` to the parent bead; `blocks` on the final document stage chore (`document`, falling back to the last stage in `pipeline.json`).
29
31
  - **Purpose**: human review of the agent PR (agent branch → feature branch) before the feature closes.
30
32
 
33
+ ## Hierarchy
34
+
35
+ Ownership is `--parent` at create time; ordering is `bd dep add`. The full shape for a
36
+ workstream, from container down to stage chore:
37
+
38
+ | Level | Type | Execution | Created by | Ownership |
39
+ |---|---|---|---|---|
40
+ | Feature | `feature` | `execution:hitl` | `create-task` step 5 | — |
41
+ | Planning bead ("Plan: <feature title>") | `task` | `execution:hitl` | `create-task` step 6 | `--parent <feature>` |
42
+ | Impl task (one per deliverable) | `task` | `execution:afk` | `create-task` step 6 | `--parent <feature>` |
43
+ | Stage chore | `chore` | inherited `execution:afk` | `create-chores.mjs` | `--parent <impl-task>` |
44
+ | PR gate | `task` | `execution:hitl` | `create-chores.mjs` | `--parent <impl-task>`, `--no-inherit-labels` |
45
+
46
+ Stage chores come from running `create-chores.mjs` once per impl task (`--parent <impl-task>`),
47
+ never against the feature itself — its parent must carry `execution:afk`.
48
+
49
+ Every impl task depends on the planning bead (`bd dep add <impl-task> <planning-bead>`), so
50
+ `bd ready` withholds them until planning closes. Nothing aggregates a child's execution class
51
+ onto its parent: the container is human-owned because a person decides when it is done, and
52
+ each child states the class of its own work.
53
+
54
+ ### Planning bead body
55
+
56
+ Cold-start ready — another person picks it up with no chat history:
57
+
58
+ ```markdown
59
+ ## What and why
60
+
61
+ The deliverable this feature produces and the problem it solves.
62
+
63
+ ## Decisions already made
64
+
65
+ - Decision — rationale
66
+
67
+ ## Open questions / risks
68
+
69
+ - Question — who answers it
70
+
71
+ ## References
72
+
73
+ - Bead IDs, files, docs consulted
74
+
75
+ ## Acceptance Criteria
76
+
77
+ - [ ] The user confirms planning is sufficient for this feature.
78
+ ```
79
+
31
80
  ## Current pipeline stages
32
81
 
33
82
  | id | title | dependsOn |