@codedology/code-forge 0.1.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 (184) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/README.md +127 -0
  4. package/bin/code-forge.mjs +145 -0
  5. package/docs/README.md +37 -0
  6. package/docs/reference/blocks.json +288 -0
  7. package/docs/reference/config.md +162 -0
  8. package/examples/first-use/README.md +11 -0
  9. package/examples/first-use/clamp.acceptance.yml +8 -0
  10. package/examples/first-use/clamp.brief.md +16 -0
  11. package/examples/first-use/coder/src/math.mjs +31 -0
  12. package/examples/first-use/coder/test/clamp.test.mjs +26 -0
  13. package/examples/node-lib/.env.example +3 -0
  14. package/examples/node-lib/.gitattributes +3 -0
  15. package/examples/node-lib/.github/workflows/test.yml +11 -0
  16. package/examples/node-lib/CHANGELOG.md +13 -0
  17. package/examples/node-lib/README.md +17 -0
  18. package/examples/node-lib/gitignore +3 -0
  19. package/examples/node-lib/package.json +19 -0
  20. package/examples/node-lib/src/list.mjs +23 -0
  21. package/examples/node-lib/src/math.mjs +21 -0
  22. package/examples/node-lib/src/strings.mjs +34 -0
  23. package/examples/node-lib/test/list.test.mjs +12 -0
  24. package/examples/node-lib/test/math.test.mjs +13 -0
  25. package/examples/node-lib/test/package.test.mjs +18 -0
  26. package/examples/node-lib/test/strings.test.mjs +28 -0
  27. package/package.json +52 -0
  28. package/schema/code-forge.schema.json +331 -0
  29. package/schema/facts.schema.json +28 -0
  30. package/scripts/first-use.mjs +517 -0
  31. package/scripts/gen-config-doc.mjs +241 -0
  32. package/skill/SKILL.md +79 -0
  33. package/skill/references/adapters/claude-code.md +19 -0
  34. package/skill/references/adapters/solo.md +34 -0
  35. package/skill/references/adapters/subprocess.md +15 -0
  36. package/skill/references/code.md +64 -0
  37. package/skill/references/continuity.md +31 -0
  38. package/skill/references/decisions.md +50 -0
  39. package/skill/references/degraded.md +40 -0
  40. package/skill/references/harden.md +26 -0
  41. package/skill/references/ledger.md +23 -0
  42. package/skill/references/plan.md +43 -0
  43. package/skill/references/proof.md +34 -0
  44. package/skill/references/review.md +42 -0
  45. package/skill/references/rules-core.md +72 -0
  46. package/skill/references/security.md +29 -0
  47. package/skill/scripts/forge +40 -0
  48. package/skill/templates/block-record.md +19 -0
  49. package/skill/templates/facts-sheet.md +22 -0
  50. package/skill/templates/plan.md +53 -0
  51. package/src/cli/author.mjs +87 -0
  52. package/src/cli/block.mjs +262 -0
  53. package/src/cli/doctor.mjs +74 -0
  54. package/src/cli/facts.mjs +124 -0
  55. package/src/cli/gates.mjs +280 -0
  56. package/src/cli/help.mjs +48 -0
  57. package/src/cli/init.mjs +14 -0
  58. package/src/cli/jev.mjs +175 -0
  59. package/src/cli/keys.mjs +170 -0
  60. package/src/cli/ledger.mjs +160 -0
  61. package/src/cli/list.mjs +68 -0
  62. package/src/cli/models.mjs +131 -0
  63. package/src/cli/plan.mjs +70 -0
  64. package/src/cli/proof.mjs +316 -0
  65. package/src/cli/remove.mjs +76 -0
  66. package/src/cli/report.mjs +73 -0
  67. package/src/cli/resolve.mjs +79 -0
  68. package/src/cli/review-file.mjs +152 -0
  69. package/src/cli/run.mjs +159 -0
  70. package/src/cli/s2.mjs +93 -0
  71. package/src/cli/spawn.mjs +104 -0
  72. package/src/cli/upgrade.mjs +103 -0
  73. package/src/cli/validate.mjs +108 -0
  74. package/src/cli/version.mjs +18 -0
  75. package/src/cli/worker.mjs +76 -0
  76. package/src/config/defaults/anthropic.mjs +18 -0
  77. package/src/config/defaults/index.mjs +24 -0
  78. package/src/config/defaults/openai.mjs +16 -0
  79. package/src/config/defaults/xai.mjs +18 -0
  80. package/src/config/known-ids.mjs +171 -0
  81. package/src/config/load.mjs +135 -0
  82. package/src/config/migrate.mjs +46 -0
  83. package/src/config/refresh.mjs +276 -0
  84. package/src/config/schema-compile.mjs +211 -0
  85. package/src/config/schema-paths.mjs +84 -0
  86. package/src/config/secret-patterns.mjs +85 -0
  87. package/src/config/validate.mjs +694 -0
  88. package/src/decide/author-loop.mjs +90 -0
  89. package/src/decide/escalation.mjs +262 -0
  90. package/src/decide/fallback-rules.mjs +172 -0
  91. package/src/decide/jev-client.mjs +216 -0
  92. package/src/decide/questions.mjs +161 -0
  93. package/src/decide/thresholds.mjs +199 -0
  94. package/src/doctor/index.mjs +105 -0
  95. package/src/doctor/local.mjs +162 -0
  96. package/src/doctor/probes.mjs +358 -0
  97. package/src/doctor/rows.mjs +51 -0
  98. package/src/doctor/worker-probe.mjs +125 -0
  99. package/src/engines/alias-map.mjs +78 -0
  100. package/src/engines/builders/claude.mjs +224 -0
  101. package/src/engines/builders/codex.mjs +242 -0
  102. package/src/engines/builders/grok.mjs +132 -0
  103. package/src/engines/builders/index.mjs +35 -0
  104. package/src/engines/builders/validate-params.mjs +34 -0
  105. package/src/engines/codex-home.mjs +230 -0
  106. package/src/engines/isolation.mjs +84 -0
  107. package/src/engines/probe.mjs +116 -0
  108. package/src/engines/provider-cli.mjs +24 -0
  109. package/src/engines/render-rules.mjs +41 -0
  110. package/src/engines/sentinel.mjs +133 -0
  111. package/src/engines/usage-parse.mjs +149 -0
  112. package/src/gates/acceptance.mjs +95 -0
  113. package/src/gates/detect.mjs +187 -0
  114. package/src/gates/invariants.mjs +43 -0
  115. package/src/gates/run.mjs +72 -0
  116. package/src/gates/safe-edit.mjs +266 -0
  117. package/src/gates/scope.mjs +154 -0
  118. package/src/gates/secret-scan.mjs +79 -0
  119. package/src/gates/transcript-grep.mjs +27 -0
  120. package/src/install/agent-env.mjs +76 -0
  121. package/src/install/detect.mjs +121 -0
  122. package/src/install/harnesses.mjs +131 -0
  123. package/src/install/link.mjs +463 -0
  124. package/src/install/wizard/answers.mjs +389 -0
  125. package/src/install/wizard/flags.mjs +248 -0
  126. package/src/install/wizard/project-file.mjs +128 -0
  127. package/src/install/wizard/run.mjs +366 -0
  128. package/src/install/wizard/steps.mjs +201 -0
  129. package/src/keys/backends/constants.mjs +7 -0
  130. package/src/keys/backends/file.mjs +99 -0
  131. package/src/keys/backends/keychain.mjs +81 -0
  132. package/src/keys/backends/security-cli.mjs +105 -0
  133. package/src/keys/onepassword.mjs +46 -0
  134. package/src/keys/store.mjs +347 -0
  135. package/src/ledger/calibration.mjs +109 -0
  136. package/src/ledger/export.mjs +52 -0
  137. package/src/ledger/outcome.mjs +397 -0
  138. package/src/ledger/paths.mjs +30 -0
  139. package/src/ledger/prices.mjs +49 -0
  140. package/src/ledger/report.mjs +250 -0
  141. package/src/ledger/write.mjs +279 -0
  142. package/src/proof/export.mjs +358 -0
  143. package/src/proof/lock.mjs +138 -0
  144. package/src/proof/red-green.mjs +519 -0
  145. package/src/proof/red-parse.mjs +103 -0
  146. package/src/proof/tiers.mjs +62 -0
  147. package/src/review/budget.mjs +241 -0
  148. package/src/review/context.mjs +156 -0
  149. package/src/review/engine.mjs +264 -0
  150. package/src/review/finding.schema.json +47 -0
  151. package/src/review/fixloop.mjs +467 -0
  152. package/src/review/gate-check.mjs +249 -0
  153. package/src/review/lenses/A.md +22 -0
  154. package/src/review/lenses/B.md +23 -0
  155. package/src/review/lenses/full.md +23 -0
  156. package/src/review/lenses/judge.md +21 -0
  157. package/src/review/lenses/quick.md +21 -0
  158. package/src/review/lenses/recheck.md +23 -0
  159. package/src/review/packet.mjs +355 -0
  160. package/src/review/triage.mjs +149 -0
  161. package/src/review/validate-review.mjs +70 -0
  162. package/src/session/author.mjs +228 -0
  163. package/src/session/facts.mjs +855 -0
  164. package/src/session/plan-check.mjs +294 -0
  165. package/src/session/s2.mjs +164 -0
  166. package/src/session/spawn.mjs +610 -0
  167. package/src/state/block.mjs +289 -0
  168. package/src/state/cli-args.mjs +67 -0
  169. package/src/state/paths.mjs +92 -0
  170. package/src/state/registry.mjs +189 -0
  171. package/src/state/run.mjs +266 -0
  172. package/src/state/signer.mjs +132 -0
  173. package/src/util/exec.mjs +315 -0
  174. package/src/util/forbidden.mjs +787 -0
  175. package/src/util/git.mjs +381 -0
  176. package/src/util/log.mjs +100 -0
  177. package/src/util/reaper.mjs +300 -0
  178. package/src/util/redact.mjs +212 -0
  179. package/src/util/tmp.mjs +409 -0
  180. package/src/worker/engine.mjs +338 -0
  181. package/src/worker/loop.mjs +392 -0
  182. package/src/worker/queue.mjs +186 -0
  183. package/src/worker/review-state.mjs +145 -0
  184. package/src/worker/ticket.mjs +195 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Harness detection (plan §2.1 "Detect" column): `<command>` on PATH OR `~/<homeMarker>/`
3
+ * exists. Deliberately never shells out (`which`/`where`) — this module scans `PATH` directory
4
+ * entries itself, which is both faster and, critically, testable under a FAKE `PATH`/`home`
5
+ * without ever touching the real `$PATH` or the real `~/.claude`, `~/.codex`, `~/.grok`,
6
+ * `~/.gemini`, `~/.agents` (coder-rules.md rule 10 — never inspect the real harness dirs in tests
7
+ * or manual runs).
8
+ */
9
+
10
+ import { access, constants, stat } from 'node:fs/promises';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import { HARNESSES } from './harnesses.mjs';
14
+
15
+ /** Windows executable suffixes probed when no extension is already present (PATHEXT convention). */
16
+ const DEFAULT_WIN32_EXTS = ['.EXE', '.CMD', '.BAT', '.COM'];
17
+
18
+ /**
19
+ * @param {string} pathEnv - a `PATH`-shaped string (colon- or semicolon-separated).
20
+ * @param {NodeJS.Platform} platform
21
+ * @returns {string[]} non-empty directory entries, in order, duplicates kept (matches shell PATH
22
+ * resolution: the first match wins, so order must survive).
23
+ */
24
+ function splitPathEnv(pathEnv, platform) {
25
+ const sep = platform === 'win32' ? ';' : ':';
26
+ return pathEnv.split(sep).filter((entry) => entry.length > 0);
27
+ }
28
+
29
+ /**
30
+ * Whether `command` resolves to an executable file in `opts.pathEnv`. Never spawns a process.
31
+ * @param {string} command
32
+ * @param {object} [opts]
33
+ * @param {string} [opts.pathEnv] - defaults to `process.env.PATH`; tests pass a fake one.
34
+ * @param {NodeJS.Platform} [opts.platform] - defaults to `process.platform`.
35
+ * @param {string} [opts.pathExt] - Windows only; defaults to `process.env.PATHEXT`.
36
+ * @returns {Promise<boolean>}
37
+ */
38
+ export async function commandOnPath(command, opts = {}) {
39
+ if (typeof command !== 'string' || command.length === 0) {
40
+ return false;
41
+ }
42
+ const platform = opts.platform ?? process.platform;
43
+ const pathEnv = opts.pathEnv ?? process.env.PATH ?? '';
44
+ const dirs = splitPathEnv(pathEnv, platform);
45
+ const exts =
46
+ platform === 'win32'
47
+ ? (opts.pathExt ?? process.env.PATHEXT ?? DEFAULT_WIN32_EXTS.join(';')).split(';').filter((e) => e.length > 0)
48
+ : [''];
49
+
50
+ for (const dir of dirs) {
51
+ for (const ext of exts) {
52
+ const candidate = path.join(dir, `${command}${ext}`);
53
+ try {
54
+ // Sequential, not Promise.all: PATH entries are searched in order and the first match
55
+ // must win, matching real shell PATH resolution.
56
+ await access(candidate, constants.X_OK);
57
+ return true;
58
+ } catch {
59
+ // not this directory — keep looking.
60
+ }
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Whether `<home>/<harness.homeMarker>` exists and is a directory.
68
+ * @param {import('./harnesses.mjs').HarnessRow} harness
69
+ * @param {object} [opts]
70
+ * @param {string} [opts.home] - defaults to `os.homedir()`.
71
+ * @returns {Promise<boolean>}
72
+ */
73
+ export async function homeMarkerPresent(harness, opts = {}) {
74
+ const home = opts.home ?? os.homedir();
75
+ try {
76
+ const info = await stat(path.join(home, harness.homeMarker));
77
+ return info.isDirectory();
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * @typedef {object} DetectionResult
85
+ * @property {string} id
86
+ * @property {boolean} detected - `onPath || homeDirPresent`.
87
+ * @property {boolean} onPath
88
+ * @property {boolean} homeDirPresent
89
+ */
90
+
91
+ /**
92
+ * @param {import('./harnesses.mjs').HarnessRow} harness
93
+ * @param {object} [opts]
94
+ * @param {string} [opts.home]
95
+ * @param {string} [opts.pathEnv]
96
+ * @param {NodeJS.Platform} [opts.platform]
97
+ * @returns {Promise<DetectionResult>}
98
+ */
99
+ export async function detectHarness(harness, opts = {}) {
100
+ const [onPath, homeDirPresent] = await Promise.all([
101
+ harness.command ? commandOnPath(harness.command, opts) : Promise.resolve(false),
102
+ homeMarkerPresent(harness, opts),
103
+ ]);
104
+ return { id: harness.id, detected: onPath || homeDirPresent, onPath, homeDirPresent };
105
+ }
106
+
107
+ /**
108
+ * Detects every row of {@link HARNESSES}, in table order.
109
+ * @param {object} [opts]
110
+ * @param {string} [opts.home]
111
+ * @param {string} [opts.pathEnv]
112
+ * @param {NodeJS.Platform} [opts.platform]
113
+ * @returns {Promise<DetectionResult[]>}
114
+ */
115
+ export async function detectAll(opts = {}) {
116
+ const results = [];
117
+ for (const harness of HARNESSES) {
118
+ results.push(await detectHarness(harness, opts));
119
+ }
120
+ return results;
121
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The per-harness install path table (plan §2.1, unchanged from v1; research/installer-patterns.md
3
+ * §"Agent Skills standard + `npx skills`"). Pure data — no filesystem access here (that is
4
+ * `./detect.mjs`) and no writes (that is `./link.mjs`).
5
+ *
6
+ * Claude Code and Codex CLI's paths are backed by assumption `[A7]` (both skill directories were
7
+ * observed on this machine, 2026-09-24: `~/.claude/skills` and `~/.codex/skills`, the latter
8
+ * empty). Grok's GLOBAL path is also backed by `[A7]` (`~/.grok/skills` observed with 5 entries,
9
+ * so Grok reads that directory) but its PROJECT path is not — a harness CLI exists to probe it
10
+ * (`doctor`, a later block, asks Grok to list its skills). Gemini is not installed on this machine
11
+ * at all: `[A6]`, its whole row is `verified: false`, matching the plan's "Gemini row UNVERIFIED"
12
+ * (§10.3 acceptance) — the only row that flag applies to. Cursor and Copilot have no CLI and no
13
+ * adapter; detection is directory-presence only, and their paths (shared with Codex/Gemini under
14
+ * `.agents/skills/`) come from research/installer-patterns.md, not from an observation on this
15
+ * machine, but the plan does not ask `doctor` to mark them unverified (there is no CLI to probe).
16
+ */
17
+
18
+ import path from 'node:path';
19
+
20
+ /**
21
+ * @typedef {object} HarnessRow
22
+ * @property {string} id - stable identifier, also the config `harnesses[]` entry value.
23
+ * @property {string} label - human-readable name.
24
+ * @property {string|null} command - executable name to look for on PATH, or `null` when the
25
+ * harness has no CLI (Cursor, Copilot) — detection then relies on `homeMarker` alone.
26
+ * @property {string} homeMarker - directory name directly under `$HOME` (e.g. `.claude`) used
27
+ * both as a detection fallback and as the base of the global install path.
28
+ * @property {string} projectSkillsDir - project-relative directory that holds installed skills.
29
+ * @property {boolean} verified - whether this row's paths are confirmed on a real machine (§10.3:
30
+ * exactly the Gemini row is `false`).
31
+ * @property {string} notes - the plan's free-text note for this row, unchanged wording.
32
+ */
33
+
34
+ /**
35
+ * Six rows, Claude Code first (plan §2.1 table order). Frozen so a caller cannot mutate the
36
+ * shared table in place.
37
+ * @type {ReadonlyArray<HarnessRow>}
38
+ */
39
+ export const HARNESSES = Object.freeze(
40
+ [
41
+ {
42
+ id: 'claude',
43
+ label: 'Claude Code',
44
+ command: 'claude',
45
+ homeMarker: '.claude',
46
+ projectSkillsDir: '.claude/skills',
47
+ verified: true,
48
+ notes: '[A7] global skills dir exists here.',
49
+ },
50
+ {
51
+ id: 'codex',
52
+ label: 'Codex CLI',
53
+ command: 'codex',
54
+ homeMarker: '.codex',
55
+ projectSkillsDir: '.agents/skills',
56
+ verified: true,
57
+ notes: '[A7] ~/.codex/skills exists (empty). Paths from research/installer-patterns.md.',
58
+ },
59
+ {
60
+ id: 'grok',
61
+ label: 'Grok CLI',
62
+ command: 'grok',
63
+ homeMarker: '.grok',
64
+ projectSkillsDir: '.agents/skills',
65
+ verified: true,
66
+ notes:
67
+ '[A7] ~/.grok/skills exists with 5 entries, so Grok reads it; the PROJECT path is unverified — doctor asks Grok to list its skills.',
68
+ },
69
+ {
70
+ id: 'gemini',
71
+ label: 'Gemini CLI',
72
+ command: 'gemini',
73
+ homeMarker: '.gemini',
74
+ projectSkillsDir: '.agents/skills',
75
+ verified: false,
76
+ notes: '[A6] not installed on this machine; path from research, UNVERIFIED.',
77
+ },
78
+ {
79
+ id: 'cursor',
80
+ label: 'Cursor',
81
+ command: null,
82
+ homeMarker: '.cursor',
83
+ projectSkillsDir: '.agents/skills',
84
+ verified: true,
85
+ notes: 'detection only; no adapter (no CLI).',
86
+ },
87
+ {
88
+ id: 'copilot',
89
+ label: 'Copilot',
90
+ command: null,
91
+ homeMarker: '.copilot',
92
+ projectSkillsDir: '.agents/skills',
93
+ verified: true,
94
+ notes: 'detection only; no adapter (no CLI).',
95
+ },
96
+ ].map((row) => Object.freeze(row)),
97
+ );
98
+
99
+ /**
100
+ * @param {string} id
101
+ * @returns {HarnessRow}
102
+ * @throws {RangeError} when `id` names no row in {@link HARNESSES}.
103
+ */
104
+ export function getHarness(id) {
105
+ const row = HARNESSES.find((h) => h.id === id);
106
+ if (!row) {
107
+ throw new RangeError(`install: unknown harness "${id}"`);
108
+ }
109
+ return row;
110
+ }
111
+
112
+ /**
113
+ * @param {HarnessRow} harness
114
+ * @param {string} projectRoot - absolute path to the project.
115
+ * @param {string} [skillName]
116
+ * @returns {string} absolute path where the skill would be installed inside the project.
117
+ */
118
+ export function projectSkillPath(harness, projectRoot, skillName = 'code-forge') {
119
+ return path.join(projectRoot, harness.projectSkillsDir, skillName);
120
+ }
121
+
122
+ /**
123
+ * @param {HarnessRow} harness
124
+ * @param {string} home - absolute path to `$HOME`.
125
+ * @param {string} [skillName]
126
+ * @returns {string} absolute path where the skill would be installed globally, under
127
+ * `<home>/<homeMarker>/skills/<skillName>`.
128
+ */
129
+ export function globalSkillPath(harness, home, skillName = 'code-forge') {
130
+ return path.join(home, harness.homeMarker, 'skills', skillName);
131
+ }