@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.
- package/.prodify/AGENTS.md +56 -0
- package/.prodify/README.md +10 -0
- package/.prodify/artifacts/01-understand.md +28 -0
- package/.prodify/artifacts/02-diagnose.md +26 -0
- package/.prodify/artifacts/03-architecture.md +30 -0
- package/.prodify/artifacts/04-plan.md +35 -0
- package/.prodify/artifacts/05-refactor.md +24 -0
- package/.prodify/artifacts/06-validate.md +17 -0
- package/.prodify/artifacts/README.md +6 -0
- package/.prodify/artifacts/architecture_spec.md +29 -0
- package/.prodify/artifacts/artifact-validation-design.md +276 -0
- package/.prodify/artifacts/cli-command-design.md +299 -0
- package/.prodify/artifacts/completed-steps-tracking.md +201 -0
- package/.prodify/artifacts/diagnostic_report.md +45 -0
- package/.prodify/artifacts/hardening-patch-summary.md +57 -0
- package/.prodify/artifacts/hardening-verification-report.md +48 -0
- package/.prodify/artifacts/implementation_summary.md +19 -0
- package/.prodify/artifacts/improvement-evaluation-spec.md +148 -0
- package/.prodify/artifacts/next-step-resolver-design.md +570 -0
- package/.prodify/artifacts/orientation_map.md +32 -0
- package/.prodify/artifacts/persona-removal-audit.md +106 -0
- package/.prodify/artifacts/planning-alignment-report.md +189 -0
- package/.prodify/artifacts/refactor-plan-template-hardening.md +83 -0
- package/.prodify/artifacts/refactor-validate-loop-design.md +231 -0
- package/.prodify/artifacts/refactor_plan.md +21 -0
- package/.prodify/artifacts/refactor_plan.strict-example.md +31 -0
- package/.prodify/artifacts/run-state-design.md +292 -0
- package/.prodify/artifacts/run-summary-spec.md +149 -0
- package/.prodify/artifacts/run_state.json +14 -0
- package/.prodify/artifacts/sample-repo-test-plan.md +129 -0
- package/.prodify/artifacts/status-output-spec.md +349 -0
- package/.prodify/artifacts/step-selection-design.md +251 -0
- package/.prodify/artifacts/task-dispatcher-design.md +266 -0
- package/.prodify/artifacts/task-header-audit.md +42 -0
- package/.prodify/artifacts/task-log-enhancement-spec.md +264 -0
- package/.prodify/artifacts/task-protocol-hardening-plan.md +68 -0
- package/.prodify/artifacts/task-self-validation-spec.md +171 -0
- package/.prodify/artifacts/task_log.json +160 -0
- package/.prodify/artifacts/template-usage-audit.md +20 -0
- package/.prodify/artifacts/validation_report.md +22 -0
- package/.prodify/contracts/README.md +3 -0
- package/.prodify/contracts/architecture.contract.json +42 -0
- package/.prodify/contracts/diagnose.contract.json +42 -0
- package/.prodify/contracts/plan.contract.json +42 -0
- package/.prodify/contracts/refactor.contract.json +45 -0
- package/.prodify/contracts/understand.contract.json +42 -0
- package/.prodify/contracts/validate.contract.json +52 -0
- package/.prodify/contracts-src/README.md +4 -0
- package/.prodify/contracts-src/architecture.contract.md +29 -0
- package/.prodify/contracts-src/diagnose.contract.md +29 -0
- package/.prodify/contracts-src/plan.contract.md +29 -0
- package/.prodify/contracts-src/refactor.contract.md +32 -0
- package/.prodify/contracts-src/understand.contract.md +29 -0
- package/.prodify/contracts-src/validate.contract.md +35 -0
- package/.prodify/metrics/README.md +4 -0
- package/.prodify/planning.md +13 -0
- package/.prodify/project.md +15 -0
- package/.prodify/rules/01-global-operating-rules.md +21 -0
- package/.prodify/rules/02-analysis-discipline.md +17 -0
- package/.prodify/rules/03-diagnosis-severity-rules.md +42 -0
- package/.prodify/rules/04-architecture-rules.md +27 -0
- package/.prodify/rules/05-planning-rules.md +23 -0
- package/.prodify/rules/06-refactor-rules.md +20 -0
- package/.prodify/rules/07-validation-rules.md +25 -0
- package/.prodify/rules/08-output-format-rules.md +20 -0
- package/.prodify/rules/09-template-usage-rules.md +11 -0
- package/.prodify/rules/10-artifact-flow-and-orchestration-rules.md +40 -0
- package/.prodify/rules/README.md +3 -0
- package/.prodify/rules/example-rule.md +4 -0
- package/.prodify/runtime-commands.md +57 -0
- package/.prodify/skills/README.md +8 -0
- package/.prodify/skills/domain/react-frontend.skill.json +34 -0
- package/.prodify/skills/domain/typescript-backend.skill.json +34 -0
- package/.prodify/skills/quality-policy/maintainability-review.skill.json +25 -0
- package/.prodify/skills/quality-policy/security-hardening.skill.json +32 -0
- package/.prodify/skills/quality-policy/test-hardening.skill.json +23 -0
- package/.prodify/skills/registry.json +16 -0
- package/.prodify/skills/stage-method/architecture-method.skill.json +22 -0
- package/.prodify/skills/stage-method/codebase-scanning.skill.json +22 -0
- package/.prodify/skills/stage-method/diagnosis-method.skill.json +22 -0
- package/.prodify/skills/stage-method/planning-method.skill.json +22 -0
- package/.prodify/skills/stage-method/refactoring-method.skill.json +22 -0
- package/.prodify/skills/stage-method/validation-method.skill.json +22 -0
- package/.prodify/state.json +30 -0
- package/.prodify/tasks/01-understand.md +83 -0
- package/.prodify/tasks/02-diagnose.md +67 -0
- package/.prodify/tasks/03-architecture.md +70 -0
- package/.prodify/tasks/04-plan.md +71 -0
- package/.prodify/tasks/05-refactor.md +61 -0
- package/.prodify/tasks/06-validate.md +69 -0
- package/.prodify/tasks/README.md +3 -0
- package/.prodify/tasks/example-task.md +13 -0
- package/.prodify/templates/01-understand.template.md +18 -0
- package/.prodify/templates/02-diagnose.template.md +18 -0
- package/.prodify/templates/03-architecture.template.md +18 -0
- package/.prodify/templates/04-plan.template.md +22 -0
- package/.prodify/templates/05-refactor.template.md +19 -0
- package/.prodify/templates/06-validate.template.md +16 -0
- package/.prodify/templates/README.md +3 -0
- package/.prodify/templates/architecture_spec.template.md +29 -0
- package/.prodify/templates/diagnostic_report.template.md +45 -0
- package/.prodify/templates/example.template.md +5 -0
- package/.prodify/templates/implementation_summary.template.md +19 -0
- package/.prodify/templates/orientation_map.template.md +32 -0
- package/.prodify/templates/refactor_plan.template.md +24 -0
- package/.prodify/templates/validation_report.template.md +22 -0
- package/.prodify/version.json +5 -0
- package/AGENTS.md +305 -0
- package/LICENSE +201 -0
- package/README.md +118 -0
- package/assets/presets/default/canonical/AGENTS.md +54 -0
- package/assets/presets/default/canonical/artifacts/README.md +6 -0
- package/assets/presets/default/canonical/contracts-src/README.md +4 -0
- package/assets/presets/default/canonical/contracts-src/architecture.contract.md +56 -0
- package/assets/presets/default/canonical/contracts-src/diagnose.contract.md +49 -0
- package/assets/presets/default/canonical/contracts-src/plan.contract.md +42 -0
- package/assets/presets/default/canonical/contracts-src/refactor.contract.md +54 -0
- package/assets/presets/default/canonical/contracts-src/understand.contract.md +56 -0
- package/assets/presets/default/canonical/contracts-src/validate.contract.md +64 -0
- package/assets/presets/default/canonical/metrics/README.md +4 -0
- package/assets/presets/default/canonical/planning.md +13 -0
- package/assets/presets/default/canonical/project.md +15 -0
- package/assets/presets/default/canonical/rules/README.md +3 -0
- package/assets/presets/default/canonical/rules/example-rule.md +4 -0
- package/assets/presets/default/canonical/runtime-commands.md +57 -0
- package/assets/presets/default/canonical/skills/README.md +8 -0
- package/assets/presets/default/canonical/skills/domain/react-frontend.skill.json +34 -0
- package/assets/presets/default/canonical/skills/domain/typescript-backend.skill.json +34 -0
- package/assets/presets/default/canonical/skills/quality-policy/maintainability-review.skill.json +25 -0
- package/assets/presets/default/canonical/skills/quality-policy/security-hardening.skill.json +32 -0
- package/assets/presets/default/canonical/skills/quality-policy/test-hardening.skill.json +23 -0
- package/assets/presets/default/canonical/skills/registry.json +16 -0
- package/assets/presets/default/canonical/skills/stage-method/architecture-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/codebase-scanning.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/diagnosis-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/planning-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/refactoring-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/validation-method.skill.json +22 -0
- package/assets/presets/default/canonical/state.json +30 -0
- package/assets/presets/default/canonical/tasks/README.md +3 -0
- package/assets/presets/default/canonical/tasks/example-task.md +13 -0
- package/assets/presets/default/canonical/templates/README.md +3 -0
- package/assets/presets/default/canonical/templates/example.template.md +5 -0
- package/assets/presets/default/preset.json +5 -0
- package/dist/cli.js +53 -0
- package/dist/commands/doctor.js +16 -0
- package/dist/commands/init.js +30 -0
- package/dist/commands/install.js +27 -0
- package/dist/commands/setup-agent.js +23 -0
- package/dist/commands/status.js +28 -0
- package/dist/commands/sync.js +29 -0
- package/dist/commands/update.js +18 -0
- package/dist/contracts/compiled-schema.js +37 -0
- package/dist/contracts/compiler.js +83 -0
- package/dist/contracts/freshness.js +52 -0
- package/dist/contracts/index.js +5 -0
- package/dist/contracts/parser.js +201 -0
- package/dist/contracts/schema.js +138 -0
- package/dist/contracts/source-schema.js +111 -0
- package/dist/core/agent-runtime.js +36 -0
- package/dist/core/agent-setup.js +111 -0
- package/dist/core/doctor.js +155 -0
- package/dist/core/errors.js +19 -0
- package/dist/core/flow-state.js +262 -0
- package/dist/core/fs.js +50 -0
- package/dist/core/install.js +44 -0
- package/dist/core/managed-files.js +106 -0
- package/dist/core/paths.js +56 -0
- package/dist/core/preset-validation.js +43 -0
- package/dist/core/prompt-builder.js +63 -0
- package/dist/core/repo-context.js +77 -0
- package/dist/core/repo-root.js +55 -0
- package/dist/core/skill-resolution.js +123 -0
- package/dist/core/state.js +220 -0
- package/dist/core/status.js +293 -0
- package/dist/core/sync.js +63 -0
- package/dist/core/targets.js +48 -0
- package/dist/core/upgrade.js +57 -0
- package/dist/core/validation.js +138 -0
- package/dist/core/version-checks.js +35 -0
- package/dist/generators/claude.js +14 -0
- package/dist/generators/codex.js +14 -0
- package/dist/generators/copilot.js +29 -0
- package/dist/generators/header.js +13 -0
- package/dist/generators/opencode.js +14 -0
- package/dist/generators/shared.js +37 -0
- package/dist/index.js +9 -0
- package/dist/legacy/targets.js +55 -0
- package/dist/presets/default.js +3 -0
- package/dist/presets/loader.js +34 -0
- package/dist/presets/version.js +18 -0
- package/dist/scoring/model.js +262 -0
- package/dist/skills/loader.js +40 -0
- package/dist/skills/schema.js +159 -0
- package/dist/types.js +1 -0
- package/docs/canonical-prodify-layout.md +87 -0
- package/docs/claude-support.md +22 -0
- package/docs/cli-doctor-spec.md +52 -0
- package/docs/cli-init-spec.md +70 -0
- package/docs/cli-install-agent-spec.md +48 -0
- package/docs/cli-sync-spec.md +40 -0
- package/docs/codex-support.md +24 -0
- package/docs/compatibility-targets.md +59 -0
- package/docs/copilot-support.md +30 -0
- package/docs/default-preset.md +47 -0
- package/docs/generated-file-headers.md +45 -0
- package/docs/generation-rules.md +94 -0
- package/docs/idempotency-guarantees.md +31 -0
- package/docs/managed-file-detection.md +45 -0
- package/docs/manual-edit-conflicts.md +37 -0
- package/docs/opencode-support.md +27 -0
- package/docs/ownership-rules.md +39 -0
- package/docs/path-resolution-rules.md +40 -0
- package/docs/preset-structure.md +49 -0
- package/docs/skill-system.md +67 -0
- package/docs/versioning-and-upgrade-strategy.md +40 -0
- package/package.json +22 -0
- package/src/README.md +11 -0
- package/src/cli.ts +61 -0
- package/src/commands/doctor.ts +20 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/setup-agent.ts +28 -0
- package/src/commands/status.ts +34 -0
- package/src/commands/update.ts +23 -0
- package/src/contracts/README.md +10 -0
- package/src/contracts/compiled-schema.ts +42 -0
- package/src/contracts/compiler.ts +111 -0
- package/src/contracts/freshness.ts +58 -0
- package/src/contracts/index.ts +11 -0
- package/src/contracts/parser.ts +253 -0
- package/src/contracts/source-schema.ts +141 -0
- package/src/core/agent-runtime.ts +53 -0
- package/src/core/agent-setup.ts +147 -0
- package/src/core/doctor.ts +171 -0
- package/src/core/errors.ts +28 -0
- package/src/core/flow-state.ts +333 -0
- package/src/core/fs.ts +59 -0
- package/src/core/paths.ts +63 -0
- package/src/core/preset-validation.ts +47 -0
- package/src/core/prompt-builder.ts +73 -0
- package/src/core/repo-context.ts +93 -0
- package/src/core/repo-root.ts +74 -0
- package/src/core/skill-resolution.ts +151 -0
- package/src/core/state.ts +264 -0
- package/src/core/status.ts +372 -0
- package/src/core/targets.ts +53 -0
- package/src/core/upgrade.ts +66 -0
- package/src/core/validation.ts +233 -0
- package/src/core/version-checks.ts +40 -0
- package/src/index.ts +13 -0
- package/src/presets/default.ts +7 -0
- package/src/presets/loader.ts +46 -0
- package/src/presets/version.ts +31 -0
- package/src/scoring/model.ts +332 -0
- package/src/skills/loader.ts +58 -0
- package/src/skills/schema.ts +197 -0
- package/src/types.ts +329 -0
- package/tests/integration/cli-flows.test.js +347 -0
- package/tests/unit/agent-setup.test.js +81 -0
- package/tests/unit/cli.test.js +28 -0
- package/tests/unit/contracts.test.js +61 -0
- package/tests/unit/helpers.js +28 -0
- package/tests/unit/paths.test.js +52 -0
- package/tests/unit/preset-loader.test.js +37 -0
- package/tests/unit/scoring.test.js +65 -0
- package/tests/unit/self-hosted-workspace.test.js +22 -0
- package/tests/unit/skills.test.js +115 -0
- package/tests/unit/state-and-flow.test.js +120 -0
- package/tests/unit/targets.test.js +13 -0
- package/tests/unit/validation.test.js +73 -0
- package/tests/unit/version.test.js +24 -0
- package/tsconfig.json +23 -0
- 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
|
+
});
|