@urielsh/prodify 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 (273) hide show
  1. package/.prodify/AGENTS.md +56 -0
  2. package/.prodify/README.md +10 -0
  3. package/.prodify/artifacts/01-understand.md +28 -0
  4. package/.prodify/artifacts/02-diagnose.md +26 -0
  5. package/.prodify/artifacts/03-architecture.md +30 -0
  6. package/.prodify/artifacts/04-plan.md +35 -0
  7. package/.prodify/artifacts/05-refactor.md +24 -0
  8. package/.prodify/artifacts/06-validate.md +17 -0
  9. package/.prodify/artifacts/README.md +6 -0
  10. package/.prodify/artifacts/architecture_spec.md +29 -0
  11. package/.prodify/artifacts/artifact-validation-design.md +276 -0
  12. package/.prodify/artifacts/cli-command-design.md +299 -0
  13. package/.prodify/artifacts/completed-steps-tracking.md +201 -0
  14. package/.prodify/artifacts/diagnostic_report.md +45 -0
  15. package/.prodify/artifacts/hardening-patch-summary.md +57 -0
  16. package/.prodify/artifacts/hardening-verification-report.md +48 -0
  17. package/.prodify/artifacts/implementation_summary.md +19 -0
  18. package/.prodify/artifacts/improvement-evaluation-spec.md +148 -0
  19. package/.prodify/artifacts/next-step-resolver-design.md +570 -0
  20. package/.prodify/artifacts/orientation_map.md +32 -0
  21. package/.prodify/artifacts/persona-removal-audit.md +106 -0
  22. package/.prodify/artifacts/planning-alignment-report.md +189 -0
  23. package/.prodify/artifacts/refactor-plan-template-hardening.md +83 -0
  24. package/.prodify/artifacts/refactor-validate-loop-design.md +231 -0
  25. package/.prodify/artifacts/refactor_plan.md +21 -0
  26. package/.prodify/artifacts/refactor_plan.strict-example.md +31 -0
  27. package/.prodify/artifacts/run-state-design.md +292 -0
  28. package/.prodify/artifacts/run-summary-spec.md +149 -0
  29. package/.prodify/artifacts/run_state.json +14 -0
  30. package/.prodify/artifacts/sample-repo-test-plan.md +129 -0
  31. package/.prodify/artifacts/status-output-spec.md +349 -0
  32. package/.prodify/artifacts/step-selection-design.md +251 -0
  33. package/.prodify/artifacts/task-dispatcher-design.md +266 -0
  34. package/.prodify/artifacts/task-header-audit.md +42 -0
  35. package/.prodify/artifacts/task-log-enhancement-spec.md +264 -0
  36. package/.prodify/artifacts/task-protocol-hardening-plan.md +68 -0
  37. package/.prodify/artifacts/task-self-validation-spec.md +171 -0
  38. package/.prodify/artifacts/task_log.json +160 -0
  39. package/.prodify/artifacts/template-usage-audit.md +20 -0
  40. package/.prodify/artifacts/validation_report.md +22 -0
  41. package/.prodify/contracts/README.md +3 -0
  42. package/.prodify/contracts/architecture.contract.json +42 -0
  43. package/.prodify/contracts/diagnose.contract.json +42 -0
  44. package/.prodify/contracts/plan.contract.json +42 -0
  45. package/.prodify/contracts/refactor.contract.json +45 -0
  46. package/.prodify/contracts/understand.contract.json +42 -0
  47. package/.prodify/contracts/validate.contract.json +52 -0
  48. package/.prodify/contracts-src/README.md +4 -0
  49. package/.prodify/contracts-src/architecture.contract.md +29 -0
  50. package/.prodify/contracts-src/diagnose.contract.md +29 -0
  51. package/.prodify/contracts-src/plan.contract.md +29 -0
  52. package/.prodify/contracts-src/refactor.contract.md +32 -0
  53. package/.prodify/contracts-src/understand.contract.md +29 -0
  54. package/.prodify/contracts-src/validate.contract.md +35 -0
  55. package/.prodify/metrics/README.md +4 -0
  56. package/.prodify/planning.md +13 -0
  57. package/.prodify/project.md +15 -0
  58. package/.prodify/rules/01-global-operating-rules.md +21 -0
  59. package/.prodify/rules/02-analysis-discipline.md +17 -0
  60. package/.prodify/rules/03-diagnosis-severity-rules.md +42 -0
  61. package/.prodify/rules/04-architecture-rules.md +27 -0
  62. package/.prodify/rules/05-planning-rules.md +23 -0
  63. package/.prodify/rules/06-refactor-rules.md +20 -0
  64. package/.prodify/rules/07-validation-rules.md +25 -0
  65. package/.prodify/rules/08-output-format-rules.md +20 -0
  66. package/.prodify/rules/09-template-usage-rules.md +11 -0
  67. package/.prodify/rules/10-artifact-flow-and-orchestration-rules.md +40 -0
  68. package/.prodify/rules/README.md +3 -0
  69. package/.prodify/rules/example-rule.md +4 -0
  70. package/.prodify/runtime-commands.md +57 -0
  71. package/.prodify/skills/README.md +8 -0
  72. package/.prodify/skills/domain/react-frontend.skill.json +34 -0
  73. package/.prodify/skills/domain/typescript-backend.skill.json +34 -0
  74. package/.prodify/skills/quality-policy/maintainability-review.skill.json +25 -0
  75. package/.prodify/skills/quality-policy/security-hardening.skill.json +32 -0
  76. package/.prodify/skills/quality-policy/test-hardening.skill.json +23 -0
  77. package/.prodify/skills/registry.json +16 -0
  78. package/.prodify/skills/stage-method/architecture-method.skill.json +22 -0
  79. package/.prodify/skills/stage-method/codebase-scanning.skill.json +22 -0
  80. package/.prodify/skills/stage-method/diagnosis-method.skill.json +22 -0
  81. package/.prodify/skills/stage-method/planning-method.skill.json +22 -0
  82. package/.prodify/skills/stage-method/refactoring-method.skill.json +22 -0
  83. package/.prodify/skills/stage-method/validation-method.skill.json +22 -0
  84. package/.prodify/state.json +30 -0
  85. package/.prodify/tasks/01-understand.md +83 -0
  86. package/.prodify/tasks/02-diagnose.md +67 -0
  87. package/.prodify/tasks/03-architecture.md +70 -0
  88. package/.prodify/tasks/04-plan.md +71 -0
  89. package/.prodify/tasks/05-refactor.md +61 -0
  90. package/.prodify/tasks/06-validate.md +69 -0
  91. package/.prodify/tasks/README.md +3 -0
  92. package/.prodify/tasks/example-task.md +13 -0
  93. package/.prodify/templates/01-understand.template.md +18 -0
  94. package/.prodify/templates/02-diagnose.template.md +18 -0
  95. package/.prodify/templates/03-architecture.template.md +18 -0
  96. package/.prodify/templates/04-plan.template.md +22 -0
  97. package/.prodify/templates/05-refactor.template.md +19 -0
  98. package/.prodify/templates/06-validate.template.md +16 -0
  99. package/.prodify/templates/README.md +3 -0
  100. package/.prodify/templates/architecture_spec.template.md +29 -0
  101. package/.prodify/templates/diagnostic_report.template.md +45 -0
  102. package/.prodify/templates/example.template.md +5 -0
  103. package/.prodify/templates/implementation_summary.template.md +19 -0
  104. package/.prodify/templates/orientation_map.template.md +32 -0
  105. package/.prodify/templates/refactor_plan.template.md +24 -0
  106. package/.prodify/templates/validation_report.template.md +22 -0
  107. package/.prodify/version.json +5 -0
  108. package/AGENTS.md +305 -0
  109. package/LICENSE +201 -0
  110. package/README.md +118 -0
  111. package/assets/presets/default/canonical/AGENTS.md +54 -0
  112. package/assets/presets/default/canonical/artifacts/README.md +6 -0
  113. package/assets/presets/default/canonical/contracts-src/README.md +4 -0
  114. package/assets/presets/default/canonical/contracts-src/architecture.contract.md +56 -0
  115. package/assets/presets/default/canonical/contracts-src/diagnose.contract.md +49 -0
  116. package/assets/presets/default/canonical/contracts-src/plan.contract.md +42 -0
  117. package/assets/presets/default/canonical/contracts-src/refactor.contract.md +54 -0
  118. package/assets/presets/default/canonical/contracts-src/understand.contract.md +56 -0
  119. package/assets/presets/default/canonical/contracts-src/validate.contract.md +64 -0
  120. package/assets/presets/default/canonical/metrics/README.md +4 -0
  121. package/assets/presets/default/canonical/planning.md +13 -0
  122. package/assets/presets/default/canonical/project.md +15 -0
  123. package/assets/presets/default/canonical/rules/README.md +3 -0
  124. package/assets/presets/default/canonical/rules/example-rule.md +4 -0
  125. package/assets/presets/default/canonical/runtime-commands.md +57 -0
  126. package/assets/presets/default/canonical/skills/README.md +8 -0
  127. package/assets/presets/default/canonical/skills/domain/react-frontend.skill.json +34 -0
  128. package/assets/presets/default/canonical/skills/domain/typescript-backend.skill.json +34 -0
  129. package/assets/presets/default/canonical/skills/quality-policy/maintainability-review.skill.json +25 -0
  130. package/assets/presets/default/canonical/skills/quality-policy/security-hardening.skill.json +32 -0
  131. package/assets/presets/default/canonical/skills/quality-policy/test-hardening.skill.json +23 -0
  132. package/assets/presets/default/canonical/skills/registry.json +16 -0
  133. package/assets/presets/default/canonical/skills/stage-method/architecture-method.skill.json +22 -0
  134. package/assets/presets/default/canonical/skills/stage-method/codebase-scanning.skill.json +22 -0
  135. package/assets/presets/default/canonical/skills/stage-method/diagnosis-method.skill.json +22 -0
  136. package/assets/presets/default/canonical/skills/stage-method/planning-method.skill.json +22 -0
  137. package/assets/presets/default/canonical/skills/stage-method/refactoring-method.skill.json +22 -0
  138. package/assets/presets/default/canonical/skills/stage-method/validation-method.skill.json +22 -0
  139. package/assets/presets/default/canonical/state.json +30 -0
  140. package/assets/presets/default/canonical/tasks/README.md +3 -0
  141. package/assets/presets/default/canonical/tasks/example-task.md +13 -0
  142. package/assets/presets/default/canonical/templates/README.md +3 -0
  143. package/assets/presets/default/canonical/templates/example.template.md +5 -0
  144. package/assets/presets/default/preset.json +5 -0
  145. package/dist/cli.js +53 -0
  146. package/dist/commands/doctor.js +16 -0
  147. package/dist/commands/init.js +30 -0
  148. package/dist/commands/install.js +27 -0
  149. package/dist/commands/setup-agent.js +23 -0
  150. package/dist/commands/status.js +28 -0
  151. package/dist/commands/sync.js +29 -0
  152. package/dist/commands/update.js +18 -0
  153. package/dist/contracts/compiled-schema.js +37 -0
  154. package/dist/contracts/compiler.js +83 -0
  155. package/dist/contracts/freshness.js +52 -0
  156. package/dist/contracts/index.js +5 -0
  157. package/dist/contracts/parser.js +201 -0
  158. package/dist/contracts/schema.js +138 -0
  159. package/dist/contracts/source-schema.js +111 -0
  160. package/dist/core/agent-runtime.js +36 -0
  161. package/dist/core/agent-setup.js +111 -0
  162. package/dist/core/doctor.js +155 -0
  163. package/dist/core/errors.js +19 -0
  164. package/dist/core/flow-state.js +262 -0
  165. package/dist/core/fs.js +50 -0
  166. package/dist/core/install.js +44 -0
  167. package/dist/core/managed-files.js +106 -0
  168. package/dist/core/paths.js +56 -0
  169. package/dist/core/preset-validation.js +43 -0
  170. package/dist/core/prompt-builder.js +63 -0
  171. package/dist/core/repo-context.js +77 -0
  172. package/dist/core/repo-root.js +55 -0
  173. package/dist/core/skill-resolution.js +123 -0
  174. package/dist/core/state.js +220 -0
  175. package/dist/core/status.js +293 -0
  176. package/dist/core/sync.js +63 -0
  177. package/dist/core/targets.js +48 -0
  178. package/dist/core/upgrade.js +57 -0
  179. package/dist/core/validation.js +138 -0
  180. package/dist/core/version-checks.js +35 -0
  181. package/dist/generators/claude.js +14 -0
  182. package/dist/generators/codex.js +14 -0
  183. package/dist/generators/copilot.js +29 -0
  184. package/dist/generators/header.js +13 -0
  185. package/dist/generators/opencode.js +14 -0
  186. package/dist/generators/shared.js +37 -0
  187. package/dist/index.js +9 -0
  188. package/dist/legacy/targets.js +55 -0
  189. package/dist/presets/default.js +3 -0
  190. package/dist/presets/loader.js +34 -0
  191. package/dist/presets/version.js +18 -0
  192. package/dist/scoring/model.js +262 -0
  193. package/dist/skills/loader.js +40 -0
  194. package/dist/skills/schema.js +159 -0
  195. package/dist/types.js +1 -0
  196. package/docs/canonical-prodify-layout.md +87 -0
  197. package/docs/claude-support.md +22 -0
  198. package/docs/cli-doctor-spec.md +52 -0
  199. package/docs/cli-init-spec.md +70 -0
  200. package/docs/cli-install-agent-spec.md +48 -0
  201. package/docs/cli-sync-spec.md +40 -0
  202. package/docs/codex-support.md +24 -0
  203. package/docs/compatibility-targets.md +59 -0
  204. package/docs/copilot-support.md +30 -0
  205. package/docs/default-preset.md +47 -0
  206. package/docs/generated-file-headers.md +45 -0
  207. package/docs/generation-rules.md +94 -0
  208. package/docs/idempotency-guarantees.md +31 -0
  209. package/docs/managed-file-detection.md +45 -0
  210. package/docs/manual-edit-conflicts.md +37 -0
  211. package/docs/opencode-support.md +27 -0
  212. package/docs/ownership-rules.md +39 -0
  213. package/docs/path-resolution-rules.md +40 -0
  214. package/docs/preset-structure.md +49 -0
  215. package/docs/skill-system.md +67 -0
  216. package/docs/versioning-and-upgrade-strategy.md +40 -0
  217. package/package.json +22 -0
  218. package/src/README.md +11 -0
  219. package/src/cli.ts +61 -0
  220. package/src/commands/doctor.ts +20 -0
  221. package/src/commands/init.ts +37 -0
  222. package/src/commands/setup-agent.ts +28 -0
  223. package/src/commands/status.ts +34 -0
  224. package/src/commands/update.ts +23 -0
  225. package/src/contracts/README.md +10 -0
  226. package/src/contracts/compiled-schema.ts +42 -0
  227. package/src/contracts/compiler.ts +111 -0
  228. package/src/contracts/freshness.ts +58 -0
  229. package/src/contracts/index.ts +11 -0
  230. package/src/contracts/parser.ts +253 -0
  231. package/src/contracts/source-schema.ts +141 -0
  232. package/src/core/agent-runtime.ts +53 -0
  233. package/src/core/agent-setup.ts +147 -0
  234. package/src/core/doctor.ts +171 -0
  235. package/src/core/errors.ts +28 -0
  236. package/src/core/flow-state.ts +333 -0
  237. package/src/core/fs.ts +59 -0
  238. package/src/core/paths.ts +63 -0
  239. package/src/core/preset-validation.ts +47 -0
  240. package/src/core/prompt-builder.ts +73 -0
  241. package/src/core/repo-context.ts +93 -0
  242. package/src/core/repo-root.ts +74 -0
  243. package/src/core/skill-resolution.ts +151 -0
  244. package/src/core/state.ts +264 -0
  245. package/src/core/status.ts +372 -0
  246. package/src/core/targets.ts +53 -0
  247. package/src/core/upgrade.ts +66 -0
  248. package/src/core/validation.ts +233 -0
  249. package/src/core/version-checks.ts +40 -0
  250. package/src/index.ts +13 -0
  251. package/src/presets/default.ts +7 -0
  252. package/src/presets/loader.ts +46 -0
  253. package/src/presets/version.ts +31 -0
  254. package/src/scoring/model.ts +332 -0
  255. package/src/skills/loader.ts +58 -0
  256. package/src/skills/schema.ts +197 -0
  257. package/src/types.ts +329 -0
  258. package/tests/integration/cli-flows.test.js +347 -0
  259. package/tests/unit/agent-setup.test.js +81 -0
  260. package/tests/unit/cli.test.js +28 -0
  261. package/tests/unit/contracts.test.js +61 -0
  262. package/tests/unit/helpers.js +28 -0
  263. package/tests/unit/paths.test.js +52 -0
  264. package/tests/unit/preset-loader.test.js +37 -0
  265. package/tests/unit/scoring.test.js +65 -0
  266. package/tests/unit/self-hosted-workspace.test.js +22 -0
  267. package/tests/unit/skills.test.js +115 -0
  268. package/tests/unit/state-and-flow.test.js +120 -0
  269. package/tests/unit/targets.test.js +13 -0
  270. package/tests/unit/validation.test.js +73 -0
  271. package/tests/unit/version.test.js +24 -0
  272. package/tsconfig.json +23 -0
  273. package/urielsh-prodify-0.1.0.tgz +0 -0
@@ -0,0 +1,81 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ import { createTempDir } from './helpers.js';
7
+ import {
8
+ createInitialGlobalAgentSetupState,
9
+ listConfiguredAgents,
10
+ readGlobalAgentSetupState,
11
+ resolveGlobalAgentSetupStatePath,
12
+ setupAgentIntegration
13
+ } from '../../dist/core/agent-setup.js';
14
+ import { detectRuntimeAgentFromEnv, resolveRuntimeAgentBinding } from '../../dist/core/agent-runtime.js';
15
+
16
+ test('global agent setup registers multiple agents without repo-local state', async () => {
17
+ const root = await createTempDir();
18
+ const env = {
19
+ ...process.env,
20
+ PRODIFY_HOME: path.join(root, '.prodify-home')
21
+ };
22
+
23
+ await setupAgentIntegration('codex', {
24
+ now: '2026-04-04T00:00:00.000Z',
25
+ env
26
+ });
27
+ await setupAgentIntegration('claude', {
28
+ now: '2026-04-04T00:05:00.000Z',
29
+ env
30
+ });
31
+
32
+ const state = await readGlobalAgentSetupState({
33
+ env
34
+ });
35
+
36
+ assert.deepEqual(listConfiguredAgents(state), ['claude', 'codex']);
37
+ await fs.access(resolveGlobalAgentSetupStatePath(env));
38
+ await assert.rejects(fs.access(path.join(root, '.prodify')));
39
+ });
40
+
41
+ test('runtime agent binding resolves from explicit, env, and single configured agent inputs', async () => {
42
+ const root = await createTempDir();
43
+ const env = {
44
+ ...process.env,
45
+ PRODIFY_HOME: path.join(root, '.prodify-home')
46
+ };
47
+
48
+ await setupAgentIntegration('copilot', {
49
+ now: '2026-04-04T00:10:00.000Z',
50
+ env
51
+ });
52
+
53
+ assert.equal(await resolveRuntimeAgentBinding({
54
+ requestedAgent: 'claude',
55
+ env
56
+ }), 'claude');
57
+ assert.equal(detectRuntimeAgentFromEnv({
58
+ ...env,
59
+ PRODIFY_ACTIVE_AGENT: 'opencode'
60
+ }), 'opencode');
61
+ assert.equal(await resolveRuntimeAgentBinding({
62
+ env
63
+ }), 'copilot');
64
+ });
65
+
66
+ test('runtime agent binding rejects ambiguous or missing global setup', async () => {
67
+ const root = await createTempDir();
68
+ const env = {
69
+ ...process.env,
70
+ PRODIFY_HOME: path.join(root, '.prodify-home')
71
+ };
72
+
73
+ assert.deepEqual(listConfiguredAgents(createInitialGlobalAgentSetupState()), []);
74
+
75
+ await assert.rejects(resolveRuntimeAgentBinding({ env }), /setup-agent/);
76
+
77
+ await setupAgentIntegration('codex', { env });
78
+ await setupAgentIntegration('claude', { env });
79
+
80
+ await assert.rejects(resolveRuntimeAgentBinding({ env }), /Multiple agents/);
81
+ });
@@ -0,0 +1,28 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { COMMANDS, PUBLIC_COMMANDS, renderHelp, runCli } from '../../dist/cli.js';
5
+ import { memoryStream } from './helpers.js';
6
+
7
+ test('cli help includes all command names', async () => {
8
+ const stdout = memoryStream();
9
+ const stderr = memoryStream();
10
+ const exitCode = await runCli(['--help'], { cwd: process.cwd(), stdout, stderr });
11
+
12
+ assert.equal(exitCode, 0);
13
+ assert.match(stdout.toString(), /\bsetup-agent\b/);
14
+ assert.match(stdout.toString(), /\binit\b/);
15
+ assert.match(stdout.toString(), /\bstatus\b/);
16
+ assert.match(stdout.toString(), /\bdoctor\b/);
17
+ assert.match(stdout.toString(), /\bupdate\b/);
18
+ assert.doesNotMatch(stdout.toString(), /\binstall\b/);
19
+ assert.doesNotMatch(stdout.toString(), /\bsync\b/);
20
+ });
21
+
22
+ test('command registry exposes only the lifecycle commands publicly', () => {
23
+ assert.deepEqual(PUBLIC_COMMANDS, ['init', 'setup-agent', 'status', 'doctor', 'update']);
24
+ assert.deepEqual(Object.keys(COMMANDS).sort(), ['doctor', 'init', 'setup-agent', 'status', 'update']);
25
+ assert.match(renderHelp(), /prodify setup-agent/);
26
+ assert.match(renderHelp(), /prodify status/);
27
+ assert.match(renderHelp(), /prodify update/);
28
+ });
@@ -0,0 +1,61 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { compileContractSource, serializeCompiledContract } from '../../dist/contracts/compiler.js';
5
+
6
+ const validSource = `---
7
+ schema_version: 1
8
+ contract_version: 1.0.0
9
+ stage: understand
10
+ task_id: 01-understand
11
+ required_artifacts:
12
+ - path: .prodify/artifacts/01-understand.md
13
+ format: markdown
14
+ required_sections:
15
+ - Policy Checks
16
+ - Repository Summary
17
+ - Success Criteria
18
+ allowed_write_roots:
19
+ - .prodify/artifacts/
20
+ forbidden_writes:
21
+ - src/
22
+ policy_rules:
23
+ - Operate only on verified data.
24
+ success_criteria:
25
+ - The repository intent is captured clearly.
26
+ ---
27
+ # Understand
28
+
29
+ Human-readable rationale lives here.
30
+ `;
31
+
32
+ test('valid Markdown source contracts compile deterministically to strict runtime JSON', () => {
33
+ const contract = compileContractSource({
34
+ markdown: validSource,
35
+ sourcePath: '.prodify/contracts-src/understand.contract.md'
36
+ });
37
+
38
+ assert.equal(contract.stage, 'understand');
39
+ assert.equal(contract.task_id, '01-understand');
40
+ assert.equal(contract.required_artifacts.length, 1);
41
+ assert.equal(contract.required_artifacts[0].format, 'markdown');
42
+ assert.equal(
43
+ serializeCompiledContract(contract),
44
+ serializeCompiledContract(compileContractSource({
45
+ markdown: validSource,
46
+ sourcePath: '.prodify/contracts-src/understand.contract.md'
47
+ }))
48
+ );
49
+ });
50
+
51
+ test('invalid frontmatter fails with a precise contract error', () => {
52
+ const invalidSource = validSource.replace('task_id: 01-understand', 'task_id: 02-diagnose');
53
+
54
+ assert.throws(
55
+ () => compileContractSource({
56
+ markdown: invalidSource,
57
+ sourcePath: '.prodify/contracts-src/understand.contract.md'
58
+ }),
59
+ /does not match stage/
60
+ );
61
+ });
@@ -0,0 +1,28 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ export async function createTempRepo(prefix = 'prodify-test-') {
6
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
7
+ await fs.mkdir(path.join(root, '.git'));
8
+ process.env.PRODIFY_HOME = path.join(root, '.prodify-home');
9
+ return root;
10
+ }
11
+
12
+ export async function createTempDir(prefix = 'prodify-test-dir-') {
13
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
14
+ process.env.PRODIFY_HOME = path.join(root, '.prodify-home');
15
+ return root;
16
+ }
17
+
18
+ export function memoryStream() {
19
+ let value = '';
20
+ return {
21
+ write(chunk) {
22
+ value += String(chunk);
23
+ },
24
+ toString() {
25
+ return value;
26
+ }
27
+ };
28
+ }
@@ -0,0 +1,52 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ import { resolveRepoRoot } from '../../dist/core/repo-root.js';
8
+ import { resolveCanonicalPath } from '../../dist/core/paths.js';
9
+ import { createTempRepo } from './helpers.js';
10
+
11
+ test('repo root resolves from .prodify presence', async () => {
12
+ const repoRoot = await createTempRepo();
13
+ await fs.mkdir(path.join(repoRoot, '.prodify'));
14
+ await fs.mkdir(path.join(repoRoot, 'nested', 'more'), { recursive: true });
15
+
16
+ const resolved = await resolveRepoRoot({
17
+ cwd: path.join(repoRoot, 'nested', 'more')
18
+ });
19
+
20
+ assert.equal(resolved, repoRoot);
21
+ });
22
+
23
+ test('repo root resolves from .git during bootstrap', async () => {
24
+ const repoRoot = await createTempRepo();
25
+ await fs.mkdir(path.join(repoRoot, 'nested', 'more'), { recursive: true });
26
+
27
+ const resolved = await resolveRepoRoot({
28
+ cwd: path.join(repoRoot, 'nested', 'more'),
29
+ allowBootstrap: true
30
+ });
31
+
32
+ assert.equal(resolved, repoRoot);
33
+ });
34
+
35
+ test('canonical paths still resolve deterministically', () => {
36
+ const repoRoot = '/tmp/example-repo';
37
+
38
+ assert.equal(resolveCanonicalPath(repoRoot, '.prodify/AGENTS.md'), '/tmp/example-repo/.prodify/AGENTS.md');
39
+ assert.equal(resolveCanonicalPath(repoRoot, '.prodify/contracts-src/understand.contract.md'), '/tmp/example-repo/.prodify/contracts-src/understand.contract.md');
40
+ assert.equal(resolveCanonicalPath(repoRoot, '.prodify/contracts/understand.contract.json'), '/tmp/example-repo/.prodify/contracts/understand.contract.json');
41
+ });
42
+
43
+ test('repo root resolution fails cleanly when root is missing', async () => {
44
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'prodify-no-root-'));
45
+
46
+ await assert.rejects(
47
+ resolveRepoRoot({ cwd: repoRoot }),
48
+ /Could not resolve repository root/
49
+ );
50
+
51
+ await fs.rm(repoRoot, { recursive: true, force: true });
52
+ });
@@ -0,0 +1,37 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { loadDefaultPreset } from '../../dist/presets/loader.js';
5
+
6
+ test('default preset loads required canonical files', async () => {
7
+ const preset = await loadDefaultPreset();
8
+ const paths = preset.entries.map((entry) => entry.relativePath);
9
+
10
+ assert.ok(paths.includes('.prodify/AGENTS.md'));
11
+ assert.ok(paths.includes('.prodify/artifacts/README.md'));
12
+ assert.ok(paths.includes('.prodify/contracts-src/README.md'));
13
+ assert.ok(paths.includes('.prodify/contracts-src/understand.contract.md'));
14
+ assert.ok(paths.includes('.prodify/contracts-src/validate.contract.md'));
15
+ assert.ok(paths.includes('.prodify/metrics/README.md'));
16
+ assert.ok(paths.includes('.prodify/project.md'));
17
+ assert.ok(paths.includes('.prodify/planning.md'));
18
+ assert.ok(paths.includes('.prodify/runtime-commands.md'));
19
+ assert.ok(paths.includes('.prodify/skills/README.md'));
20
+ assert.ok(paths.includes('.prodify/skills/registry.json'));
21
+ assert.ok(paths.includes('.prodify/skills/stage-method/codebase-scanning.skill.json'));
22
+ assert.ok(paths.includes('.prodify/state.json'));
23
+ assert.ok(paths.includes('.prodify/version.json'));
24
+ assert.ok(paths.includes('.prodify/tasks/README.md'));
25
+ assert.ok(paths.includes('.prodify/rules/README.md'));
26
+ assert.ok(paths.includes('.prodify/templates/README.md'));
27
+ assert.equal(paths.includes('AGENTS.md'), false);
28
+ assert.equal(paths.includes('CLAUDE.md'), false);
29
+ assert.equal(paths.some((entry) => entry.startsWith('.github/')), false);
30
+ assert.equal(paths.some((entry) => entry.startsWith('.opencode/')), false);
31
+ assert.equal(paths.some((entry) => entry.startsWith('.prodify/presets/')), false);
32
+
33
+ const stateEntry = preset.entries.find((entry) => entry.relativePath === '.prodify/state.json');
34
+ assert.ok(stateEntry);
35
+ assert.doesNotMatch(stateEntry.content, /primary_agent/);
36
+ assert.doesNotMatch(stateEntry.content, /selected_agent/);
37
+ });
@@ -0,0 +1,65 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ import { runCli } from '../../dist/cli.js';
7
+ import { bootstrapFlowState, startFlowExecution } from '../../dist/core/flow-state.js';
8
+ import { readRuntimeState } from '../../dist/core/state.js';
9
+ import { writeScoreDelta, writeScoreSnapshot } from '../../dist/scoring/model.js';
10
+ import { createTempRepo, memoryStream } from './helpers.js';
11
+
12
+ async function execCli(repoRoot, args) {
13
+ const stdout = memoryStream();
14
+ const stderr = memoryStream();
15
+ const exitCode = await runCli(args, { cwd: repoRoot, stdout, stderr });
16
+ return { exitCode, stdout: stdout.toString(), stderr: stderr.toString() };
17
+ }
18
+
19
+ test('baseline, final, and delta score artifacts are written under .prodify/metrics', async () => {
20
+ const repoRoot = await createTempRepo();
21
+ await execCli(repoRoot, ['init']);
22
+
23
+ const state = await readRuntimeState(repoRoot, {
24
+ presetMetadata: {
25
+ name: 'default',
26
+ version: '4.0.0',
27
+ schemaVersion: '4'
28
+ }
29
+ });
30
+ const baselineState = bootstrapFlowState(state, {
31
+ agent: 'codex',
32
+ mode: 'interactive'
33
+ });
34
+ const finalState = startFlowExecution(baselineState);
35
+ finalState.runtime.current_state = 'validate_complete';
36
+ finalState.runtime.current_stage = 'validate';
37
+ finalState.runtime.current_task_id = '06-validate';
38
+ finalState.runtime.last_validation = {
39
+ stage: 'validate',
40
+ contract_version: '1.0.0',
41
+ passed: true,
42
+ violated_rules: [],
43
+ missing_artifacts: [],
44
+ warnings: [],
45
+ diagnostics: ['ok']
46
+ };
47
+ finalState.runtime.last_validation_result = 'pass';
48
+
49
+ const baseline = await writeScoreSnapshot(repoRoot, {
50
+ kind: 'baseline',
51
+ runtimeState: baselineState
52
+ });
53
+ const final = await writeScoreSnapshot(repoRoot, {
54
+ kind: 'final',
55
+ runtimeState: finalState
56
+ });
57
+ const delta = await writeScoreDelta(repoRoot);
58
+
59
+ assert.equal(typeof baseline.total_score, 'number');
60
+ assert.equal(typeof final.total_score, 'number');
61
+ assert.equal(delta.delta, Number((final.total_score - baseline.total_score).toFixed(2)));
62
+ await fs.access(path.join(repoRoot, '.prodify', 'metrics', 'baseline.score.json'));
63
+ await fs.access(path.join(repoRoot, '.prodify', 'metrics', 'final.score.json'));
64
+ await fs.access(path.join(repoRoot, '.prodify', 'metrics', 'delta.json'));
65
+ });
@@ -0,0 +1,22 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ import { REQUIRED_CANONICAL_PATHS } from '../../dist/core/paths.js';
7
+
8
+ test('checked-in repo-root .prodify workspace contains the canonical runtime layout', async () => {
9
+ for (const relativePath of REQUIRED_CANONICAL_PATHS) {
10
+ await fs.access(path.join(process.cwd(), relativePath));
11
+ }
12
+ });
13
+
14
+ test('self-hosted workspace guidance distinguishes canonical runtime files from repo-local additions', async () => {
15
+ const artifactReadme = await fs.readFile(path.join(process.cwd(), '.prodify', 'artifacts', 'README.md'), 'utf8');
16
+ const rootGuidance = await fs.readFile(path.join(process.cwd(), '.prodify', 'AGENTS.md'), 'utf8');
17
+
18
+ assert.equal(REQUIRED_CANONICAL_PATHS.includes('AGENTS.md'), false);
19
+ assert.match(artifactReadme, /numbered filenames such as `01-understand\.md` through `06-validate\.md`/);
20
+ assert.match(artifactReadme, /repository-local design or historical artifacts/i);
21
+ assert.match(rootGuidance, /root `AGENTS\.md`.*repository-local contributor guidance/i);
22
+ });
@@ -0,0 +1,115 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ import { runCli } from '../../dist/cli.js';
7
+ import { inspectRepositoryStatus } from '../../dist/core/status.js';
8
+ import { resolveStageSkills } from '../../dist/core/skill-resolution.js';
9
+ import { loadSkillRegistry } from '../../dist/skills/loader.js';
10
+ import { validateSkillDefinitionShape } from '../../dist/skills/schema.js';
11
+ import { createTempRepo, memoryStream } from './helpers.js';
12
+
13
+ async function execCli(repoRoot, args) {
14
+ const stdout = memoryStream();
15
+ const stderr = memoryStream();
16
+ const exitCode = await runCli(args, { cwd: repoRoot, stdout, stderr });
17
+
18
+ return {
19
+ exitCode,
20
+ stdout: stdout.toString(),
21
+ stderr: stderr.toString()
22
+ };
23
+ }
24
+
25
+ async function seedTypeScriptCliRepo(repoRoot) {
26
+ await fs.mkdir(path.join(repoRoot, 'src'), { recursive: true });
27
+ await fs.writeFile(path.join(repoRoot, 'package.json'), JSON.stringify({
28
+ name: 'fixture',
29
+ version: '1.0.0',
30
+ private: true
31
+ }, null, 2));
32
+ await fs.writeFile(path.join(repoRoot, 'tsconfig.json'), JSON.stringify({
33
+ compilerOptions: {
34
+ target: 'ES2022'
35
+ }
36
+ }, null, 2));
37
+ await fs.writeFile(path.join(repoRoot, 'src', 'cli.ts'), 'export const cli = true;\n');
38
+ }
39
+
40
+ test('valid skills load from the canonical registry', async () => {
41
+ const repoRoot = await createTempRepo();
42
+ const result = await execCli(repoRoot, ['init']);
43
+ assert.equal(result.exitCode, 0);
44
+
45
+ const registry = await loadSkillRegistry(repoRoot);
46
+
47
+ assert.ok(registry.has('codebase-scanning'));
48
+ assert.ok(registry.has('typescript-backend'));
49
+ assert.ok(registry.has('test-hardening'));
50
+ });
51
+
52
+ test('invalid skills fail clearly', () => {
53
+ assert.throws(
54
+ () => validateSkillDefinitionShape({
55
+ schema_version: '1',
56
+ id: '',
57
+ name: 'Broken Skill',
58
+ version: '1.0.0',
59
+ category: 'stage-method',
60
+ description: 'broken',
61
+ intended_use: ['broken'],
62
+ stage_compatibility: ['understand'],
63
+ activation_conditions: [],
64
+ execution_guidance: ['broken'],
65
+ caution_guidance: []
66
+ }),
67
+ /must be a non-empty string/
68
+ );
69
+ });
70
+
71
+ test('stage-incompatible skills cannot be attached', async () => {
72
+ const repoRoot = await createTempRepo();
73
+ await execCli(repoRoot, ['init']);
74
+
75
+ const contractPath = path.join(repoRoot, '.prodify', 'contracts', 'plan.contract.json');
76
+ const contract = JSON.parse(await fs.readFile(contractPath, 'utf8'));
77
+ contract.skill_routing = {
78
+ default_skills: ['validation-method'],
79
+ allowed_skills: ['validation-method'],
80
+ conditional_skills: []
81
+ };
82
+ await fs.writeFile(contractPath, `${JSON.stringify(contract, null, 2)}\n`, 'utf8');
83
+
84
+ await assert.rejects(
85
+ () => resolveStageSkills(repoRoot, 'plan'),
86
+ /not compatible with stage "plan"/
87
+ );
88
+ });
89
+
90
+ test('stage default and conditional skills resolve deterministically from repo context', async () => {
91
+ const repoRoot = await createTempRepo();
92
+ await execCli(repoRoot, ['init']);
93
+ await seedTypeScriptCliRepo(repoRoot);
94
+
95
+ const resolution = await resolveStageSkills(repoRoot, 'refactor');
96
+
97
+ assert.equal(resolution.stage, 'refactor');
98
+ assert.deepEqual(resolution.active_skill_ids, ['refactoring-method', 'test-hardening', 'typescript-backend']);
99
+ assert.match(
100
+ resolution.considered_skills.find((skill) => skill.id === 'typescript-backend').reason,
101
+ /TypeScript/
102
+ );
103
+ });
104
+
105
+ test('skill routing stays agent-agnostic across status views', async () => {
106
+ const repoRoot = await createTempRepo();
107
+ await execCli(repoRoot, ['init']);
108
+ await seedTypeScriptCliRepo(repoRoot);
109
+
110
+ const codexReport = await inspectRepositoryStatus(repoRoot, { agent: 'codex' });
111
+ const claudeReport = await inspectRepositoryStatus(repoRoot, { agent: 'claude' });
112
+
113
+ assert.deepEqual(codexReport.stageSkillResolution.active_skill_ids, claudeReport.stageSkillResolution.active_skill_ids);
114
+ assert.equal(codexReport.stageSkillResolution.stage, 'understand');
115
+ });
@@ -0,0 +1,120 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { bootstrapFlowState, completeFlowStage, failFlowStage, getResumeDecision, stageToTaskId, startFlowExecution } from '../../dist/core/flow-state.js';
5
+ import { buildBootstrapPrompt, buildExecutionPrompt, buildRuntimeCommandReference } from '../../dist/core/prompt-builder.js';
6
+ import { createInitialRuntimeState } from '../../dist/core/state.js';
7
+
8
+ const presetMetadata = {
9
+ name: 'default',
10
+ version: '4.0.0',
11
+ schemaVersion: '4'
12
+ };
13
+
14
+ function passingValidation(stage) {
15
+ return {
16
+ stage,
17
+ contract_version: '1.0.0',
18
+ passed: true,
19
+ violated_rules: [],
20
+ missing_artifacts: [],
21
+ warnings: [],
22
+ diagnostics: ['ok']
23
+ };
24
+ }
25
+
26
+ test('runtime bootstrap initializes the bootstrapped checkpoint deterministically', () => {
27
+ const state = createInitialRuntimeState({ presetMetadata });
28
+ const bootstrapped = bootstrapFlowState(state, {
29
+ agent: 'codex',
30
+ mode: 'interactive',
31
+ now: '2026-03-31T00:00:00.000Z'
32
+ });
33
+
34
+ assert.equal(bootstrapped.runtime.status, 'ready');
35
+ assert.equal(bootstrapped.runtime.current_state, 'bootstrapped');
36
+ assert.equal(bootstrapped.runtime.pending_stage, 'understand');
37
+ assert.equal(bootstrapped.runtime.current_stage, null);
38
+ assert.equal(bootstrapped.runtime.next_action, '$prodify-execute');
39
+ assert.deepEqual(bootstrapped.runtime.bootstrap, {
40
+ bootstrapped: true
41
+ });
42
+ });
43
+
44
+ test('interactive execution pauses on a stage-complete checkpoint and resumes with $prodify-resume', () => {
45
+ const state = createInitialRuntimeState({ presetMetadata });
46
+ const bootstrapped = bootstrapFlowState(state, {
47
+ agent: 'codex',
48
+ mode: 'interactive'
49
+ });
50
+ const running = startFlowExecution(bootstrapped);
51
+ const paused = completeFlowStage(running, {
52
+ validation: passingValidation('understand')
53
+ });
54
+
55
+ assert.equal(paused.runtime.status, 'awaiting_validation');
56
+ assert.equal(paused.runtime.current_state, 'understand_complete');
57
+ assert.equal(paused.runtime.current_stage, 'understand');
58
+ assert.equal(paused.runtime.pending_stage, 'diagnose');
59
+ assert.equal(paused.runtime.current_task_id, '01-understand');
60
+ assert.equal(paused.runtime.next_action, '$prodify-resume');
61
+ assert.deepEqual(paused.runtime.completed_stages, ['understand']);
62
+ assert.deepEqual(getResumeDecision(paused), {
63
+ resumable: true,
64
+ command: '$prodify-resume',
65
+ reason: 'understand_complete'
66
+ });
67
+ });
68
+
69
+ test('auto execution advances to the next pending stage without a validation pause', () => {
70
+ const state = createInitialRuntimeState({ presetMetadata });
71
+ const bootstrapped = bootstrapFlowState(state, {
72
+ agent: 'codex',
73
+ mode: 'auto'
74
+ });
75
+ const running = startFlowExecution(bootstrapped, {
76
+ mode: 'auto'
77
+ });
78
+ const advanced = completeFlowStage(running, {
79
+ validation: passingValidation('understand')
80
+ });
81
+
82
+ assert.equal(advanced.runtime.status, 'ready');
83
+ assert.equal(advanced.runtime.current_state, 'diagnose_pending');
84
+ assert.equal(advanced.runtime.current_stage, 'diagnose');
85
+ assert.equal(advanced.runtime.next_action, '$prodify-execute --auto');
86
+ });
87
+
88
+ test('failed validation produces a non-resumable failed checkpoint', () => {
89
+ const state = createInitialRuntimeState({ presetMetadata });
90
+ const bootstrapped = bootstrapFlowState(state, {
91
+ agent: 'codex',
92
+ mode: 'interactive'
93
+ });
94
+ const running = startFlowExecution(bootstrapped);
95
+ const failed = failFlowStage(running, {
96
+ reason: 'missing artifact'
97
+ });
98
+
99
+ assert.equal(failed.runtime.current_state, 'failed');
100
+ assert.equal(failed.runtime.resumable, false);
101
+ assert.equal(getResumeDecision(failed).resumable, false);
102
+ });
103
+
104
+ test('prompt builder includes runtime commands and current contract checkpoint context', () => {
105
+ const state = createInitialRuntimeState({ presetMetadata });
106
+ const bootstrapped = bootstrapFlowState(state, {
107
+ agent: 'codex',
108
+ mode: 'interactive'
109
+ });
110
+
111
+ assert.match(buildRuntimeCommandReference(), /prodify setup-agent <agent>/);
112
+ assert.match(buildRuntimeCommandReference(), /\.prodify\/contracts\/\*\.contract\.json/);
113
+ assert.match(buildRuntimeCommandReference({ concise: true }), /\$prodify-resume/);
114
+ assert.match(buildBootstrapPrompt('codex'), /Read \.prodify\/AGENTS\.md/);
115
+ assert.match(buildBootstrapPrompt('claude'), /Read \.prodify\/AGENTS\.md/);
116
+ assert.match(buildBootstrapPrompt('copilot'), /Read \.prodify\/AGENTS\.md/);
117
+ assert.match(buildBootstrapPrompt('opencode'), /Read \.prodify\/AGENTS\.md/);
118
+ assert.match(buildExecutionPrompt(bootstrapped), /Current task: 01-understand/);
119
+ assert.equal(stageToTaskId('validate'), '06-validate');
120
+ });
@@ -0,0 +1,13 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { RUNTIME_PROFILES, listRuntimeProfiles } from '../../dist/core/targets.js';
5
+
6
+ test('runtime profiles expose all supported agents and bootstrap prompts', () => {
7
+ assert.deepEqual(Object.keys(RUNTIME_PROFILES).sort(), ['claude', 'codex', 'copilot', 'opencode']);
8
+
9
+ for (const profile of listRuntimeProfiles()) {
10
+ assert.match(profile.bootstrapPrompt, /\.prodify\/AGENTS\.md/);
11
+ assert.equal(profile.resumeCommand, '$prodify-resume');
12
+ }
13
+ });