@urielsh/prodify 0.1.0 → 0.1.2
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/README.md +103 -63
- package/dist/commands/setup-agent.js +3 -0
- package/dist/core/agent-setup.js +116 -1
- package/dist/core/repo-root.js +17 -8
- package/package.json +1 -1
- package/src/commands/setup-agent.ts +3 -0
- package/src/core/agent-setup.ts +126 -2
- package/src/core/repo-root.ts +21 -8
- package/tests/integration/cli-flows.test.js +18 -0
- package/tests/unit/agent-setup.test.js +9 -3
- package/urielsh-prodify-0.1.0.tgz +0 -0
package/README.md
CHANGED
|
@@ -1,92 +1,124 @@
|
|
|
1
1
|
# Prodify
|
|
2
2
|
|
|
3
|
-
Prodify turns AI-
|
|
3
|
+
Prodify turns AI- and vibe-coded repositories into production-grade repositories through a deterministic, agent-native workflow.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It gives a repo a repeatable upgrade path instead of relying on ad hoc prompting, one-off cleanup passes, or agent memory. The CLI bootstraps the runtime. The coding agent executes the staged transformation flow inside the repo.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Why Prodify
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Turn messy AI-generated repos into structured, reviewable systems.
|
|
10
|
+
- Keep runtime state, tasks, artifacts, contracts, and metrics inside `.prodify/`.
|
|
11
|
+
- Separate repository setup from in-agent execution.
|
|
12
|
+
- Validate stage outputs against compiled contracts instead of trusting freeform agent claims.
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
## Quick Start
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
- inside-agent runtime commands for actually running the contract-driven transformation flow
|
|
16
|
+
Install Prodify from npm:
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- `prodify status`
|
|
20
|
-
- `prodify doctor`
|
|
21
|
-
- `prodify update`
|
|
18
|
+
```sh
|
|
19
|
+
npm install -g @urielsh/prodify
|
|
20
|
+
```
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
Prepare your coding agent once per machine:
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
- `$prodify-execute --auto`
|
|
29
|
-
- `$prodify-resume`
|
|
24
|
+
```sh
|
|
25
|
+
prodify setup-agent codex
|
|
26
|
+
```
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
Initialize a target repository:
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Humans edit stage contracts under `.prodify/contracts-src/`.
|
|
38
|
-
- Runtime execution reads only compiled contracts under `.prodify/contracts/`.
|
|
39
|
-
- Product-owned skill definitions live under `.prodify/skills/`.
|
|
40
|
-
- Stage outputs live under `.prodify/artifacts/`.
|
|
41
|
-
- Local baseline/final/delta scoring lives under `.prodify/metrics/`.
|
|
42
|
-
- This repository’s checked-in repo-root `.prodify/` directory is the self-hosting workspace for Prodify itself. It includes the generated runtime layout plus repo-specific design and development artifacts, so it is not a byte-for-byte snapshot of fresh `prodify init` output.
|
|
30
|
+
```sh
|
|
31
|
+
cd path/to/your-repo
|
|
32
|
+
prodify init
|
|
33
|
+
```
|
|
43
34
|
|
|
44
|
-
|
|
35
|
+
Then open your coding agent and start the runtime:
|
|
45
36
|
|
|
46
|
-
|
|
37
|
+
```text
|
|
38
|
+
Read .prodify/AGENTS.md and bootstrap Prodify for this repository.
|
|
39
|
+
```
|
|
47
40
|
|
|
48
|
-
```
|
|
49
|
-
prodify
|
|
41
|
+
```text
|
|
42
|
+
$prodify-init
|
|
43
|
+
$prodify-execute
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
Use `$prodify-execute --auto` to continue without pausing between stages, or `$prodify-resume` to continue an interrupted run.
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
npm run build
|
|
56
|
-
node ./dist/index.js init
|
|
57
|
-
```
|
|
48
|
+
## Using Prodify In Your Repo
|
|
58
49
|
|
|
59
|
-
|
|
50
|
+
1. Install the npm package.
|
|
51
|
+
2. Run `prodify setup-agent <agent>` once per machine for the agent you use.
|
|
52
|
+
3. Run `prodify init` inside the repository you want to upgrade.
|
|
53
|
+
4. Open that repository in a supported coding agent.
|
|
54
|
+
5. Tell the agent to read `.prodify/AGENTS.md`.
|
|
55
|
+
6. Run `$prodify-init`, then continue with `$prodify-execute` or `$prodify-execute --auto`.
|
|
60
56
|
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- OpenCode
|
|
57
|
+
Repo initialization stays agent-agnostic. The active runtime is resolved when `$prodify-init` runs inside the opened agent, not when `prodify init` creates `.prodify/`.
|
|
58
|
+
|
|
59
|
+
## How It Works
|
|
65
60
|
|
|
66
|
-
|
|
61
|
+
- `prodify init` creates the `.prodify/` runtime workspace in the repo.
|
|
62
|
+
- The agent reads `.prodify/AGENTS.md` as the runtime entrypoint.
|
|
63
|
+
- `$prodify-init` prepares the in-agent run state.
|
|
64
|
+
- `$prodify-execute` runs one stage at a time.
|
|
65
|
+
- `$prodify-execute --auto` keeps advancing until a hard stop.
|
|
66
|
+
- `$prodify-resume` continues from the saved runtime state.
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Stage order:
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
- `understand`
|
|
71
|
+
- `diagnose`
|
|
72
|
+
- `architecture`
|
|
73
|
+
- `plan`
|
|
74
|
+
- `refactor`
|
|
75
|
+
- `validate`
|
|
76
|
+
|
|
77
|
+
## CLI Commands
|
|
78
|
+
|
|
79
|
+
- `prodify setup-agent <codex|claude|copilot|opencode>`
|
|
80
|
+
One-time machine setup for the coding agent runtime.
|
|
81
|
+
- `prodify init`
|
|
82
|
+
Create the `.prodify/` workspace in a repository.
|
|
83
|
+
- `prodify status`
|
|
84
|
+
Inspect runtime state and recommended next actions.
|
|
85
|
+
- `prodify doctor`
|
|
86
|
+
Check runtime health, generated files, and execution readiness.
|
|
87
|
+
- `prodify update`
|
|
88
|
+
Refresh the local Prodify workspace, compiled contracts, and related runtime assets.
|
|
89
|
+
|
|
90
|
+
## Agent Runtime Commands
|
|
91
|
+
|
|
92
|
+
- `$prodify-init`
|
|
93
|
+
Bootstrap Prodify inside the opened coding agent.
|
|
94
|
+
- `$prodify-execute`
|
|
95
|
+
Run the next workflow stage interactively.
|
|
96
|
+
- `$prodify-execute --auto`
|
|
97
|
+
Continue across stages until a hard stop or policy gate.
|
|
98
|
+
- `$prodify-resume`
|
|
99
|
+
Continue from the saved `.prodify/state.json` runtime state.
|
|
71
100
|
|
|
72
|
-
|
|
73
|
-
8. Use `prodify status`, `prodify doctor`, and `prodify update` from the CLI to inspect or refresh the `.prodify/` scaffolding, compiled contracts, and local metrics workspace.
|
|
101
|
+
## Repo Model
|
|
74
102
|
|
|
75
|
-
|
|
103
|
+
- `.prodify/` is the only required product-owned footprint.
|
|
104
|
+
- Durable workflow state lives in `.prodify/state.json`.
|
|
105
|
+
- Tasks live under `.prodify/tasks/`.
|
|
106
|
+
- Stage outputs live under `.prodify/artifacts/`.
|
|
107
|
+
- Skill definitions live under `.prodify/skills/`.
|
|
108
|
+
- Local baseline, final, and delta scoring artifacts live under `.prodify/metrics/`.
|
|
109
|
+
- No root-level agent files are required in the default product flow.
|
|
76
110
|
|
|
77
111
|
## Contracts And Validation
|
|
78
112
|
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
- Status can report which stage skills were considered and activated from repository context, but contracts and validators remain authoritative.
|
|
83
|
-
- Stage completion is gated by compiled-contract validation, not by agent assertion alone.
|
|
113
|
+
- Humans edit contract sources under `.prodify/contracts-src/`.
|
|
114
|
+
- Runtime execution reads only compiled contracts under `.prodify/contracts/`.
|
|
115
|
+
- Stage completion is gated by compiled-contract validation.
|
|
84
116
|
- Validation checks required artifacts, write boundaries, forbidden writes, Markdown sections, JSON keys, and contract criteria.
|
|
85
117
|
|
|
86
118
|
## Local Scoring
|
|
87
119
|
|
|
88
|
-
- Prodify can persist
|
|
89
|
-
- Raw
|
|
120
|
+
- Prodify can persist baseline, final, and delta score artifacts under `.prodify/metrics/`.
|
|
121
|
+
- Raw outputs and normalized scores are stored separately so scoring stays traceable and deterministic.
|
|
90
122
|
|
|
91
123
|
## Supported Agents
|
|
92
124
|
|
|
@@ -95,24 +127,32 @@ Repo initialization stays agent-agnostic. The active agent is resolved when `$pr
|
|
|
95
127
|
- Copilot
|
|
96
128
|
- OpenCode
|
|
97
129
|
|
|
98
|
-
All supported agents
|
|
130
|
+
All supported agents share the same bootstrap path: read `.prodify/AGENTS.md`, then run `$prodify-init`.
|
|
99
131
|
|
|
100
|
-
## Development
|
|
132
|
+
## Development / Contributing
|
|
101
133
|
|
|
102
|
-
|
|
134
|
+
Prodify users and Prodify contributors follow different entrypoints:
|
|
135
|
+
|
|
136
|
+
- Product users start from `.prodify/AGENTS.md` inside the repository they want to improve.
|
|
137
|
+
- Contributors working on the Prodify source repository follow the root [AGENTS.md](/Users/urielsh/projects/prodify/AGENTS.md).
|
|
138
|
+
|
|
139
|
+
In this repository, root `AGENTS.md` is repository-local contributor guidance. `.prodify/AGENTS.md` remains the runtime entrypoint for product users.
|
|
140
|
+
|
|
141
|
+
For this self-hosting repository, the checked-in repo-root `.prodify/` directory is a development workspace for Prodify itself, not a byte-for-byte snapshot of fresh `prodify init` output.
|
|
142
|
+
|
|
143
|
+
Run the source-repo test suite with:
|
|
103
144
|
|
|
104
145
|
```sh
|
|
105
146
|
npm test
|
|
106
147
|
```
|
|
107
148
|
|
|
108
|
-
The implementation lives
|
|
149
|
+
The main implementation lives in:
|
|
150
|
+
|
|
109
151
|
- `src/`
|
|
110
152
|
- `assets/presets/default/`
|
|
111
153
|
- `tests/`
|
|
112
154
|
- `docs/`
|
|
113
155
|
|
|
114
|
-
Repository-local contributor guidance for this self-hosted repo lives in root [AGENTS.md](/Users/urielsh/projects/prodify/AGENTS.md). Product runtime guidance lives in [.prodify/AGENTS.md](/Users/urielsh/projects/prodify/.prodify/AGENTS.md).
|
|
115
|
-
|
|
116
156
|
## License
|
|
117
157
|
|
|
118
158
|
This project is licensed under the Apache License 2.0. See [LICENSE](./LICENSE).
|
|
@@ -17,6 +17,9 @@ export async function runSetupAgentCommand(args, context) {
|
|
|
17
17
|
context.stdout.write(`Status: ${result.alreadyConfigured ? 'already configured globally; refreshed' : 'configured globally'}\n`);
|
|
18
18
|
context.stdout.write(`Configured agents: ${result.configuredAgents.join(', ')}\n`);
|
|
19
19
|
context.stdout.write(`Registry: ${result.statePath}\n`);
|
|
20
|
+
if (result.installedPaths.length > 0) {
|
|
21
|
+
context.stdout.write(`Installed runtime commands: ${result.installedPaths.join(', ')}\n`);
|
|
22
|
+
}
|
|
20
23
|
context.stdout.write('Repo impact: none\n');
|
|
21
24
|
context.stdout.write('Next step: run `prodify init` in a repository, then open that agent and use `$prodify-init`.\n');
|
|
22
25
|
return 0;
|
package/dist/core/agent-setup.js
CHANGED
|
@@ -6,6 +6,117 @@ import { pathExists, writeFileEnsuringDir } from './fs.js';
|
|
|
6
6
|
import { getRuntimeProfile } from './targets.js';
|
|
7
7
|
export const GLOBAL_AGENT_SETUP_SCHEMA_VERSION = '1';
|
|
8
8
|
export const PRODIFY_RUNTIME_COMMANDS = ['$prodify-init', '$prodify-execute', '$prodify-resume'];
|
|
9
|
+
function resolveCodexHome(env = process.env) {
|
|
10
|
+
const explicit = env.CODEX_HOME?.trim();
|
|
11
|
+
if (explicit) {
|
|
12
|
+
return path.resolve(explicit);
|
|
13
|
+
}
|
|
14
|
+
return path.join(os.homedir(), '.codex');
|
|
15
|
+
}
|
|
16
|
+
function resolveCodexSkillsRoot(env = process.env) {
|
|
17
|
+
return path.join(resolveCodexHome(env), 'skills');
|
|
18
|
+
}
|
|
19
|
+
function renderCodexSkill(name) {
|
|
20
|
+
if (name === 'prodify-init') {
|
|
21
|
+
return `---
|
|
22
|
+
name: "prodify-init"
|
|
23
|
+
description: "Bootstrap Prodify inside the current repository."
|
|
24
|
+
metadata:
|
|
25
|
+
short-description: "Bootstrap Prodify inside the current repository."
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<codex_skill_adapter>
|
|
29
|
+
## A. Skill Invocation
|
|
30
|
+
- This skill is invoked by mentioning \`$prodify-init\`.
|
|
31
|
+
- Ignore trailing arguments unless the repository-specific runtime instructions require them.
|
|
32
|
+
</codex_skill_adapter>
|
|
33
|
+
|
|
34
|
+
# prodify-init
|
|
35
|
+
|
|
36
|
+
Use this runtime bridge to bootstrap Prodify inside the current repository.
|
|
37
|
+
|
|
38
|
+
Load and follow, in this order:
|
|
39
|
+
- \`.prodify/AGENTS.md\`
|
|
40
|
+
- \`.prodify/runtime-commands.md\`
|
|
41
|
+
- \`.prodify/state.json\`
|
|
42
|
+
|
|
43
|
+
Keep the runtime anchored to \`.prodify/\`.
|
|
44
|
+
Do not substitute compatibility files for the canonical \`.prodify/AGENTS.md\` entrypoint.
|
|
45
|
+
|
|
46
|
+
Available runtime commands:
|
|
47
|
+
- \`$prodify-init\`
|
|
48
|
+
- \`$prodify-execute\`
|
|
49
|
+
- \`$prodify-execute --auto\`
|
|
50
|
+
- \`$prodify-resume\`
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
if (name === 'prodify-execute') {
|
|
54
|
+
return `---
|
|
55
|
+
name: "prodify-execute"
|
|
56
|
+
description: "Execute the next Prodify workflow stage inside the current repository."
|
|
57
|
+
metadata:
|
|
58
|
+
short-description: "Execute the next Prodify workflow stage."
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<codex_skill_adapter>
|
|
62
|
+
## A. Skill Invocation
|
|
63
|
+
- This skill is invoked by mentioning \`$prodify-execute\`.
|
|
64
|
+
- Treat all user text after \`$prodify-execute\` as \`{{PRODIFY_EXECUTE_ARGS}}\`.
|
|
65
|
+
- If no arguments are present, treat \`{{PRODIFY_EXECUTE_ARGS}}\` as empty.
|
|
66
|
+
</codex_skill_adapter>
|
|
67
|
+
|
|
68
|
+
# prodify-execute
|
|
69
|
+
|
|
70
|
+
Use this runtime bridge to execute the next Prodify workflow stage.
|
|
71
|
+
|
|
72
|
+
Load and follow, in this order:
|
|
73
|
+
- \`.prodify/runtime-commands.md\`
|
|
74
|
+
- \`.prodify/state.json\`
|
|
75
|
+
- \`.prodify/AGENTS.md\`
|
|
76
|
+
|
|
77
|
+
Interpret \`{{PRODIFY_EXECUTE_ARGS}}\` as the runtime command arguments.
|
|
78
|
+
- empty: run \`$prodify-execute\`
|
|
79
|
+
- \`--auto\`: run \`$prodify-execute --auto\`
|
|
80
|
+
|
|
81
|
+
Keep all execution state, artifacts, contracts, and validation anchored to \`.prodify/\`.
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
return `---
|
|
85
|
+
name: "prodify-resume"
|
|
86
|
+
description: "Resume a paused Prodify run from saved runtime state."
|
|
87
|
+
metadata:
|
|
88
|
+
short-description: "Resume a paused Prodify run."
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
<codex_skill_adapter>
|
|
92
|
+
## A. Skill Invocation
|
|
93
|
+
- This skill is invoked by mentioning \`$prodify-resume\`.
|
|
94
|
+
- Treat trailing arguments as optional runtime-specific hints only when the repository guidance explicitly supports them.
|
|
95
|
+
</codex_skill_adapter>
|
|
96
|
+
|
|
97
|
+
# prodify-resume
|
|
98
|
+
|
|
99
|
+
Use this runtime bridge to resume Prodify from saved runtime state.
|
|
100
|
+
|
|
101
|
+
Load and follow, in this order:
|
|
102
|
+
- \`.prodify/runtime-commands.md\`
|
|
103
|
+
- \`.prodify/state.json\`
|
|
104
|
+
- \`.prodify/AGENTS.md\`
|
|
105
|
+
|
|
106
|
+
Resume from the current state recorded under \`.prodify/state.json\`.
|
|
107
|
+
Preserve validation checkpoints and stop clearly if the state is corrupt or non-resumable.
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
async function installCodexRuntimeCommands(env = process.env) {
|
|
111
|
+
const skillsRoot = resolveCodexSkillsRoot(env);
|
|
112
|
+
const installedFiles = [];
|
|
113
|
+
for (const command of ['prodify-init', 'prodify-execute', 'prodify-resume']) {
|
|
114
|
+
const skillPath = path.join(skillsRoot, command, 'SKILL.md');
|
|
115
|
+
await writeFileEnsuringDir(skillPath, renderCodexSkill(command));
|
|
116
|
+
installedFiles.push(skillPath);
|
|
117
|
+
}
|
|
118
|
+
return installedFiles;
|
|
119
|
+
}
|
|
9
120
|
function asRecord(value) {
|
|
10
121
|
return typeof value === 'object' && value !== null ? value : {};
|
|
11
122
|
}
|
|
@@ -102,10 +213,14 @@ export async function setupAgentIntegration(agent, { now = new Date().toISOStrin
|
|
|
102
213
|
configured_at: now,
|
|
103
214
|
commands: [...PRODIFY_RUNTIME_COMMANDS]
|
|
104
215
|
};
|
|
216
|
+
const installedPaths = profile.name === 'codex'
|
|
217
|
+
? await installCodexRuntimeCommands(env)
|
|
218
|
+
: [];
|
|
105
219
|
const statePath = await writeGlobalAgentSetupState(nextState, { env });
|
|
106
220
|
return {
|
|
107
221
|
statePath,
|
|
108
222
|
configuredAgents: listConfiguredAgents(nextState),
|
|
109
|
-
alreadyConfigured
|
|
223
|
+
alreadyConfigured,
|
|
224
|
+
installedPaths
|
|
110
225
|
};
|
|
111
226
|
}
|
package/dist/core/repo-root.js
CHANGED
|
@@ -39,15 +39,24 @@ export async function resolveRepoRoot(options = {}) {
|
|
|
39
39
|
}
|
|
40
40
|
return explicitRepo;
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
let current = cwd;
|
|
43
|
+
while (true) {
|
|
44
|
+
const hasProdify = await directoryHas(current, '.prodify');
|
|
45
|
+
if (hasProdify) {
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
const hasGit = await directoryHas(current, '.git');
|
|
49
|
+
if (hasGit) {
|
|
50
|
+
if (allowBootstrap) {
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
50
54
|
}
|
|
55
|
+
const parent = path.dirname(current);
|
|
56
|
+
if (parent === current) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
current = parent;
|
|
51
60
|
}
|
|
52
61
|
throw new ProdifyError('Could not resolve repository root from the current working directory.', {
|
|
53
62
|
code: 'REPO_ROOT_NOT_FOUND'
|
package/package.json
CHANGED
|
@@ -22,6 +22,9 @@ export async function runSetupAgentCommand(args: string[], context: CommandConte
|
|
|
22
22
|
context.stdout.write(`Status: ${result.alreadyConfigured ? 'already configured globally; refreshed' : 'configured globally'}\n`);
|
|
23
23
|
context.stdout.write(`Configured agents: ${result.configuredAgents.join(', ')}\n`);
|
|
24
24
|
context.stdout.write(`Registry: ${result.statePath}\n`);
|
|
25
|
+
if (result.installedPaths.length > 0) {
|
|
26
|
+
context.stdout.write(`Installed runtime commands: ${result.installedPaths.join(', ')}\n`);
|
|
27
|
+
}
|
|
25
28
|
context.stdout.write('Repo impact: none\n');
|
|
26
29
|
context.stdout.write('Next step: run `prodify init` in a repository, then open that agent and use `$prodify-init`.\n');
|
|
27
30
|
return 0;
|
package/src/core/agent-setup.ts
CHANGED
|
@@ -10,6 +10,126 @@ import type { GlobalAgentSetupState, RuntimeProfileName } from '../types.js';
|
|
|
10
10
|
export const GLOBAL_AGENT_SETUP_SCHEMA_VERSION = '1';
|
|
11
11
|
export const PRODIFY_RUNTIME_COMMANDS = ['$prodify-init', '$prodify-execute', '$prodify-resume'] as const;
|
|
12
12
|
|
|
13
|
+
function resolveCodexHome(env: NodeJS.ProcessEnv = process.env): string {
|
|
14
|
+
const explicit = env.CODEX_HOME?.trim();
|
|
15
|
+
if (explicit) {
|
|
16
|
+
return path.resolve(explicit);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return path.join(os.homedir(), '.codex');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveCodexSkillsRoot(env: NodeJS.ProcessEnv = process.env): string {
|
|
23
|
+
return path.join(resolveCodexHome(env), 'skills');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderCodexSkill(name: 'prodify-init' | 'prodify-execute' | 'prodify-resume'): string {
|
|
27
|
+
if (name === 'prodify-init') {
|
|
28
|
+
return `---
|
|
29
|
+
name: "prodify-init"
|
|
30
|
+
description: "Bootstrap Prodify inside the current repository."
|
|
31
|
+
metadata:
|
|
32
|
+
short-description: "Bootstrap Prodify inside the current repository."
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<codex_skill_adapter>
|
|
36
|
+
## A. Skill Invocation
|
|
37
|
+
- This skill is invoked by mentioning \`$prodify-init\`.
|
|
38
|
+
- Ignore trailing arguments unless the repository-specific runtime instructions require them.
|
|
39
|
+
</codex_skill_adapter>
|
|
40
|
+
|
|
41
|
+
# prodify-init
|
|
42
|
+
|
|
43
|
+
Use this runtime bridge to bootstrap Prodify inside the current repository.
|
|
44
|
+
|
|
45
|
+
Load and follow, in this order:
|
|
46
|
+
- \`.prodify/AGENTS.md\`
|
|
47
|
+
- \`.prodify/runtime-commands.md\`
|
|
48
|
+
- \`.prodify/state.json\`
|
|
49
|
+
|
|
50
|
+
Keep the runtime anchored to \`.prodify/\`.
|
|
51
|
+
Do not substitute compatibility files for the canonical \`.prodify/AGENTS.md\` entrypoint.
|
|
52
|
+
|
|
53
|
+
Available runtime commands:
|
|
54
|
+
- \`$prodify-init\`
|
|
55
|
+
- \`$prodify-execute\`
|
|
56
|
+
- \`$prodify-execute --auto\`
|
|
57
|
+
- \`$prodify-resume\`
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (name === 'prodify-execute') {
|
|
62
|
+
return `---
|
|
63
|
+
name: "prodify-execute"
|
|
64
|
+
description: "Execute the next Prodify workflow stage inside the current repository."
|
|
65
|
+
metadata:
|
|
66
|
+
short-description: "Execute the next Prodify workflow stage."
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
<codex_skill_adapter>
|
|
70
|
+
## A. Skill Invocation
|
|
71
|
+
- This skill is invoked by mentioning \`$prodify-execute\`.
|
|
72
|
+
- Treat all user text after \`$prodify-execute\` as \`{{PRODIFY_EXECUTE_ARGS}}\`.
|
|
73
|
+
- If no arguments are present, treat \`{{PRODIFY_EXECUTE_ARGS}}\` as empty.
|
|
74
|
+
</codex_skill_adapter>
|
|
75
|
+
|
|
76
|
+
# prodify-execute
|
|
77
|
+
|
|
78
|
+
Use this runtime bridge to execute the next Prodify workflow stage.
|
|
79
|
+
|
|
80
|
+
Load and follow, in this order:
|
|
81
|
+
- \`.prodify/runtime-commands.md\`
|
|
82
|
+
- \`.prodify/state.json\`
|
|
83
|
+
- \`.prodify/AGENTS.md\`
|
|
84
|
+
|
|
85
|
+
Interpret \`{{PRODIFY_EXECUTE_ARGS}}\` as the runtime command arguments.
|
|
86
|
+
- empty: run \`$prodify-execute\`
|
|
87
|
+
- \`--auto\`: run \`$prodify-execute --auto\`
|
|
88
|
+
|
|
89
|
+
Keep all execution state, artifacts, contracts, and validation anchored to \`.prodify/\`.
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return `---
|
|
94
|
+
name: "prodify-resume"
|
|
95
|
+
description: "Resume a paused Prodify run from saved runtime state."
|
|
96
|
+
metadata:
|
|
97
|
+
short-description: "Resume a paused Prodify run."
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
<codex_skill_adapter>
|
|
101
|
+
## A. Skill Invocation
|
|
102
|
+
- This skill is invoked by mentioning \`$prodify-resume\`.
|
|
103
|
+
- Treat trailing arguments as optional runtime-specific hints only when the repository guidance explicitly supports them.
|
|
104
|
+
</codex_skill_adapter>
|
|
105
|
+
|
|
106
|
+
# prodify-resume
|
|
107
|
+
|
|
108
|
+
Use this runtime bridge to resume Prodify from saved runtime state.
|
|
109
|
+
|
|
110
|
+
Load and follow, in this order:
|
|
111
|
+
- \`.prodify/runtime-commands.md\`
|
|
112
|
+
- \`.prodify/state.json\`
|
|
113
|
+
- \`.prodify/AGENTS.md\`
|
|
114
|
+
|
|
115
|
+
Resume from the current state recorded under \`.prodify/state.json\`.
|
|
116
|
+
Preserve validation checkpoints and stop clearly if the state is corrupt or non-resumable.
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function installCodexRuntimeCommands(env: NodeJS.ProcessEnv = process.env): Promise<string[]> {
|
|
121
|
+
const skillsRoot = resolveCodexSkillsRoot(env);
|
|
122
|
+
const installedFiles: string[] = [];
|
|
123
|
+
|
|
124
|
+
for (const command of ['prodify-init', 'prodify-execute', 'prodify-resume'] as const) {
|
|
125
|
+
const skillPath = path.join(skillsRoot, command, 'SKILL.md');
|
|
126
|
+
await writeFileEnsuringDir(skillPath, renderCodexSkill(command));
|
|
127
|
+
installedFiles.push(skillPath);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return installedFiles;
|
|
131
|
+
}
|
|
132
|
+
|
|
13
133
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
14
134
|
return typeof value === 'object' && value !== null ? value as Record<string, unknown> : {};
|
|
15
135
|
}
|
|
@@ -116,7 +236,7 @@ export async function setupAgentIntegration(
|
|
|
116
236
|
now?: string;
|
|
117
237
|
env?: NodeJS.ProcessEnv;
|
|
118
238
|
} = {}
|
|
119
|
-
): Promise<{ statePath: string; configuredAgents: RuntimeProfileName[]; alreadyConfigured: boolean }> {
|
|
239
|
+
): Promise<{ statePath: string; configuredAgents: RuntimeProfileName[]; alreadyConfigured: boolean; installedPaths: string[] }> {
|
|
120
240
|
const profile = getRuntimeProfile(agent);
|
|
121
241
|
if (!profile) {
|
|
122
242
|
throw new ProdifyError('setup-agent requires <codex|claude|copilot|opencode>.', {
|
|
@@ -138,10 +258,14 @@ export async function setupAgentIntegration(
|
|
|
138
258
|
commands: [...PRODIFY_RUNTIME_COMMANDS]
|
|
139
259
|
};
|
|
140
260
|
|
|
261
|
+
const installedPaths = profile.name === 'codex'
|
|
262
|
+
? await installCodexRuntimeCommands(env)
|
|
263
|
+
: [];
|
|
141
264
|
const statePath = await writeGlobalAgentSetupState(nextState, { env });
|
|
142
265
|
return {
|
|
143
266
|
statePath,
|
|
144
267
|
configuredAgents: listConfiguredAgents(nextState),
|
|
145
|
-
alreadyConfigured
|
|
268
|
+
alreadyConfigured,
|
|
269
|
+
installedPaths
|
|
146
270
|
};
|
|
147
271
|
}
|
package/src/core/repo-root.ts
CHANGED
|
@@ -56,16 +56,29 @@ export async function resolveRepoRoot(options: ResolveRepoRootOptions = {}): Pro
|
|
|
56
56
|
return explicitRepo;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
let current = cwd;
|
|
60
|
+
|
|
61
|
+
while (true) {
|
|
62
|
+
const hasProdify = await directoryHas(current, '.prodify');
|
|
63
|
+
if (hasProdify) {
|
|
64
|
+
return current;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hasGit = await directoryHas(current, '.git');
|
|
68
|
+
if (hasGit) {
|
|
69
|
+
if (allowBootstrap) {
|
|
70
|
+
return current;
|
|
71
|
+
}
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
const gitRoot = await searchUpwards(cwd, async (candidate) => directoryHas(candidate, '.git'));
|
|
66
|
-
if (gitRoot) {
|
|
67
|
-
return gitRoot;
|
|
73
|
+
break;
|
|
68
74
|
}
|
|
75
|
+
|
|
76
|
+
const parent = path.dirname(current);
|
|
77
|
+
if (parent === current) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
current = parent;
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
throw new ProdifyError('Could not resolve repository root from the current working directory.', {
|
|
@@ -60,6 +60,19 @@ test('init creates only .prodify-owned runtime scaffolding', async () => {
|
|
|
60
60
|
await assertMissing(repoRoot, '.opencode/AGENTS.md');
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
test('init prefers the current git repo over an unrelated ancestor .prodify workspace', async () => {
|
|
64
|
+
const outerRepo = await createTempRepo('prodify-outer-');
|
|
65
|
+
await execCli(outerRepo, ['init']);
|
|
66
|
+
|
|
67
|
+
const nestedRepo = path.join(outerRepo, 'nested-app');
|
|
68
|
+
await fs.mkdir(path.join(nestedRepo, '.git'), { recursive: true });
|
|
69
|
+
|
|
70
|
+
const result = await execCli(nestedRepo, ['init']);
|
|
71
|
+
|
|
72
|
+
assert.equal(result.exitCode, 0);
|
|
73
|
+
await fs.access(path.join(nestedRepo, '.prodify', 'state.json'));
|
|
74
|
+
});
|
|
75
|
+
|
|
63
76
|
test('status becomes the primary user-facing summary after init', async () => {
|
|
64
77
|
const repoRoot = await createTempRepo();
|
|
65
78
|
await execCli(repoRoot, ['init']);
|
|
@@ -85,14 +98,19 @@ test('status becomes the primary user-facing summary after init', async () => {
|
|
|
85
98
|
|
|
86
99
|
test('setup-agent configures a supported agent globally without repo-local writes', async () => {
|
|
87
100
|
const cwd = await createTempDir();
|
|
101
|
+
process.env.CODEX_HOME = path.join(cwd, '.codex-home');
|
|
88
102
|
|
|
89
103
|
const result = await execCli(cwd, ['setup-agent', 'codex']);
|
|
90
104
|
|
|
91
105
|
assert.equal(result.exitCode, 0);
|
|
92
106
|
assert.match(result.stdout, /Prodify Agent Setup/);
|
|
93
107
|
assert.match(result.stdout, /Agent: codex/);
|
|
108
|
+
assert.match(result.stdout, /Installed runtime commands:/);
|
|
94
109
|
assert.match(result.stdout, /Repo impact: none/);
|
|
95
110
|
await fs.access(path.join(cwd, '.prodify-home', 'agent-setup.json'));
|
|
111
|
+
await fs.access(path.join(cwd, '.codex-home', 'skills', 'prodify-init', 'SKILL.md'));
|
|
112
|
+
await fs.access(path.join(cwd, '.codex-home', 'skills', 'prodify-execute', 'SKILL.md'));
|
|
113
|
+
await fs.access(path.join(cwd, '.codex-home', 'skills', 'prodify-resume', 'SKILL.md'));
|
|
96
114
|
await assertMissing(cwd, '.prodify');
|
|
97
115
|
});
|
|
98
116
|
|
|
@@ -17,10 +17,11 @@ test('global agent setup registers multiple agents without repo-local state', as
|
|
|
17
17
|
const root = await createTempDir();
|
|
18
18
|
const env = {
|
|
19
19
|
...process.env,
|
|
20
|
-
PRODIFY_HOME: path.join(root, '.prodify-home')
|
|
20
|
+
PRODIFY_HOME: path.join(root, '.prodify-home'),
|
|
21
|
+
CODEX_HOME: path.join(root, '.codex-home')
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
await setupAgentIntegration('codex', {
|
|
24
|
+
const codexResult = await setupAgentIntegration('codex', {
|
|
24
25
|
now: '2026-04-04T00:00:00.000Z',
|
|
25
26
|
env
|
|
26
27
|
});
|
|
@@ -34,7 +35,11 @@ test('global agent setup registers multiple agents without repo-local state', as
|
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
assert.deepEqual(listConfiguredAgents(state), ['claude', 'codex']);
|
|
38
|
+
assert.equal(codexResult.installedPaths.length, 3);
|
|
37
39
|
await fs.access(resolveGlobalAgentSetupStatePath(env));
|
|
40
|
+
assert.match(await fs.readFile(path.join(root, '.codex-home', 'skills', 'prodify-init', 'SKILL.md'), 'utf8'), /\$prodify-init/);
|
|
41
|
+
assert.match(await fs.readFile(path.join(root, '.codex-home', 'skills', 'prodify-execute', 'SKILL.md'), 'utf8'), /\$prodify-execute/);
|
|
42
|
+
assert.match(await fs.readFile(path.join(root, '.codex-home', 'skills', 'prodify-resume', 'SKILL.md'), 'utf8'), /\$prodify-resume/);
|
|
38
43
|
await assert.rejects(fs.access(path.join(root, '.prodify')));
|
|
39
44
|
});
|
|
40
45
|
|
|
@@ -67,7 +72,8 @@ test('runtime agent binding rejects ambiguous or missing global setup', async ()
|
|
|
67
72
|
const root = await createTempDir();
|
|
68
73
|
const env = {
|
|
69
74
|
...process.env,
|
|
70
|
-
PRODIFY_HOME: path.join(root, '.prodify-home')
|
|
75
|
+
PRODIFY_HOME: path.join(root, '.prodify-home'),
|
|
76
|
+
CODEX_HOME: path.join(root, '.codex-home')
|
|
71
77
|
};
|
|
72
78
|
|
|
73
79
|
assert.deepEqual(listConfiguredAgents(createInitialGlobalAgentSetupState()), []);
|
|
Binary file
|