@powerhousedao/ph-clint-cli 0.1.0-dev.32 → 0.1.0-dev.34
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/dist/cli.js +21 -1
- package/dist/codegen/actions.d.ts +42 -0
- package/dist/codegen/actions.js +342 -0
- package/dist/codegen/builders/cli-package-json.js +8 -4
- package/dist/codegen/builders/index.d.ts +1 -1
- package/dist/codegen/builders/index.js +1 -1
- package/dist/codegen/builders/publish-config-js.d.ts +8 -0
- package/dist/codegen/builders/publish-config-js.js +32 -0
- package/dist/codegen/builders/root-package-json.js +7 -3
- package/dist/codegen/index.d.ts +3 -0
- package/dist/codegen/index.js +13 -6
- package/dist/commands/clint-project-init.js +14 -31
- package/dist/commands/clint-project-publish.js +1 -1
- package/dist/commands/clint-project-regen.js +7 -1
- package/dist/gen/skills/cli-setup/SKILL.md +1 -1
- package/dist/gen/skills/service-definition/SKILL.md +1 -1
- package/dist/triggers/spec-change.js +6 -58
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -46,10 +46,20 @@ export const cli = defineCli({
|
|
|
46
46
|
'ph-clint-dev-agent': {
|
|
47
47
|
name: 'PhClintDevAgent',
|
|
48
48
|
sections: ['AgentBase.md'],
|
|
49
|
-
skills: ['command-definition'],
|
|
49
|
+
skills: ['cli-setup', 'command-definition', 'service-definition'],
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
52
|
skills: {
|
|
53
|
+
'cli-setup': {
|
|
54
|
+
description: 'Set up a new CLI with defineCli, config schemas, entrypoint, and interactive mode',
|
|
55
|
+
inputSchema: z.object({
|
|
56
|
+
mode: z
|
|
57
|
+
.enum(['expert', 'discovery', 'one-shot'])
|
|
58
|
+
.default('expert')
|
|
59
|
+
.describe('Expert: align design decisions with the developer. Discovery: explain concepts step by step. One-shot: make all decisions autonomously'),
|
|
60
|
+
}),
|
|
61
|
+
instructionTemplate: 'Use your {{skillId}} skill in {{mode}} mode for: {{prompt}}',
|
|
62
|
+
},
|
|
53
63
|
'command-definition': {
|
|
54
64
|
description: 'Define commands with Zod schemas, execute functions, return values, and parameter prompting',
|
|
55
65
|
inputSchema: z.object({
|
|
@@ -60,6 +70,16 @@ export const cli = defineCli({
|
|
|
60
70
|
}),
|
|
61
71
|
instructionTemplate: 'Use your {{skillId}} skill in {{mode}} mode for: {{prompt}}',
|
|
62
72
|
},
|
|
73
|
+
'service-definition': {
|
|
74
|
+
description: 'Define background services with readiness detection, preflight checks, and project scanning',
|
|
75
|
+
inputSchema: z.object({
|
|
76
|
+
mode: z
|
|
77
|
+
.enum(['expert', 'discovery', 'one-shot'])
|
|
78
|
+
.default('expert')
|
|
79
|
+
.describe('Expert: align design decisions with the developer. Discovery: explain concepts step by step. One-shot: make all decisions autonomously'),
|
|
80
|
+
}),
|
|
81
|
+
instructionTemplate: 'Use your {{skillId}} skill in {{mode}} mode for: {{prompt}}',
|
|
82
|
+
},
|
|
63
83
|
},
|
|
64
84
|
},
|
|
65
85
|
// @clint:end prompts
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { GenerateProjectResult } from './index.js';
|
|
2
|
+
import type { ClintProjectSpec, ExternalSkill } from '../spec/types.js';
|
|
3
|
+
import type { ProcessRunOptions } from '@powerhousedao/ph-clint';
|
|
4
|
+
export type PostGenAction = {
|
|
5
|
+
kind: 'ph-init';
|
|
6
|
+
targetDir: string;
|
|
7
|
+
appDir: string;
|
|
8
|
+
spec: ClintProjectSpec;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'app-install';
|
|
11
|
+
dir: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'app-build';
|
|
14
|
+
dir: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'cli-install';
|
|
17
|
+
dir: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'cli-build';
|
|
20
|
+
dir: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'skills-sync';
|
|
23
|
+
targetDir: string;
|
|
24
|
+
desired: ExternalSkill[];
|
|
25
|
+
};
|
|
26
|
+
export type PostGenActionKind = PostGenAction['kind'];
|
|
27
|
+
export interface PostGenActionContext {
|
|
28
|
+
log: (msg: string) => void;
|
|
29
|
+
runProcess?: (command: string, opts?: Omit<ProcessRunOptions, 'onOutput'>) => Promise<{
|
|
30
|
+
success: boolean;
|
|
31
|
+
output: string;
|
|
32
|
+
}>;
|
|
33
|
+
skip?: Set<PostGenActionKind>;
|
|
34
|
+
}
|
|
35
|
+
export interface PostGenActionResult {
|
|
36
|
+
action: PostGenAction;
|
|
37
|
+
status: 'success' | 'failed' | 'skipped';
|
|
38
|
+
reason?: string;
|
|
39
|
+
durationMs: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function collectPostGenActions(result: GenerateProjectResult, spec: ClintProjectSpec): PostGenAction[];
|
|
42
|
+
export declare function runPostGenActions(actions: PostGenAction[], ctx: PostGenActionContext): Promise<PostGenActionResult[]>;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-generation actions — a typed pipeline that runs after codegen.
|
|
3
|
+
*
|
|
4
|
+
* `collectPostGenActions()` inspects a `GenerateProjectResult` to determine
|
|
5
|
+
* which side-effects (install, build, ph-init, skills-sync) are needed.
|
|
6
|
+
*
|
|
7
|
+
* `runPostGenActions()` executes them sequentially with proper ordering,
|
|
8
|
+
* failure cascading, and log output.
|
|
9
|
+
*/
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Fixed execution order — index defines precedence
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const ACTION_ORDER = [
|
|
15
|
+
'ph-init',
|
|
16
|
+
'app-install',
|
|
17
|
+
'app-build',
|
|
18
|
+
'cli-install',
|
|
19
|
+
'cli-build',
|
|
20
|
+
'skills-sync',
|
|
21
|
+
];
|
|
22
|
+
function actionIndex(kind) {
|
|
23
|
+
return ACTION_ORDER.indexOf(kind);
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Dependency map — which actions depend on which (for failure cascading)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/** Returns true if `downstream` depends on `upstream` succeeding. */
|
|
29
|
+
function dependsOn(downstream, upstream) {
|
|
30
|
+
// ph-init → app-install → app-build → cli-install → cli-build
|
|
31
|
+
// skills-sync is independent of the build chain
|
|
32
|
+
const chain = [
|
|
33
|
+
'ph-init',
|
|
34
|
+
'app-install',
|
|
35
|
+
'app-build',
|
|
36
|
+
'cli-install',
|
|
37
|
+
'cli-build',
|
|
38
|
+
];
|
|
39
|
+
if (downstream === 'skills-sync')
|
|
40
|
+
return false;
|
|
41
|
+
const di = chain.indexOf(downstream);
|
|
42
|
+
const ui = chain.indexOf(upstream);
|
|
43
|
+
if (di < 0 || ui < 0)
|
|
44
|
+
return false;
|
|
45
|
+
return di > ui;
|
|
46
|
+
}
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// collectPostGenActions — pure function
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
export function collectPostGenActions(result, spec) {
|
|
51
|
+
const split = result.appDir !== null;
|
|
52
|
+
const relPaths = new Set(result.files.map((f) => f.relativePath));
|
|
53
|
+
// Determine the earliest triggered action in the chain
|
|
54
|
+
let earliest = null;
|
|
55
|
+
if (split) {
|
|
56
|
+
const appFolder = result.appDir ? path.basename(result.appDir) : '';
|
|
57
|
+
const cliFolder = path.basename(result.cliDir);
|
|
58
|
+
// ph-init needed: app dir exists but no package.json (new scaffold)
|
|
59
|
+
const appPkgJsonChanged = relPaths.has(path.join(appFolder, 'package.json'));
|
|
60
|
+
// Check for the .gitkeep — signals a new app dir that needs ph-init
|
|
61
|
+
const appGitkeepWritten = relPaths.has(path.join(appFolder, '.gitkeep'));
|
|
62
|
+
if (appGitkeepWritten && !appPkgJsonChanged) {
|
|
63
|
+
// .gitkeep written but no package.json — app needs ph init
|
|
64
|
+
earliest = 'ph-init';
|
|
65
|
+
}
|
|
66
|
+
// Migration always triggers full rebuild
|
|
67
|
+
if (result.migrated) {
|
|
68
|
+
earliest = earliest ?? 'app-install';
|
|
69
|
+
}
|
|
70
|
+
// App package.json changed
|
|
71
|
+
if (!earliest && appPkgJsonChanged) {
|
|
72
|
+
earliest = 'app-install';
|
|
73
|
+
}
|
|
74
|
+
// App source regenerated (framework.gen.ts, index.ts, etc.)
|
|
75
|
+
if (!earliest) {
|
|
76
|
+
for (const rel of relPaths) {
|
|
77
|
+
if (rel.startsWith(appFolder + '/') &&
|
|
78
|
+
rel.endsWith('.ts')) {
|
|
79
|
+
earliest = 'app-build';
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// CLI package.json changed
|
|
85
|
+
if (!earliest) {
|
|
86
|
+
if (relPaths.has(path.join(cliFolder, 'package.json'))) {
|
|
87
|
+
earliest = 'cli-install';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// CLI source regenerated (.ts files)
|
|
91
|
+
if (!earliest) {
|
|
92
|
+
for (const rel of relPaths) {
|
|
93
|
+
if (rel.startsWith(cliFolder + '/') &&
|
|
94
|
+
rel.endsWith('.ts')) {
|
|
95
|
+
earliest = 'cli-build';
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Flat layout
|
|
103
|
+
if (relPaths.has('package.json')) {
|
|
104
|
+
earliest = 'cli-install';
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
for (const rel of relPaths) {
|
|
108
|
+
if (rel.endsWith('.ts')) {
|
|
109
|
+
earliest = 'cli-build';
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Build the action list from earliest onward
|
|
116
|
+
const actions = [];
|
|
117
|
+
if (earliest && split && result.appDir) {
|
|
118
|
+
const ei = actionIndex(earliest);
|
|
119
|
+
if (ei <= actionIndex('ph-init')) {
|
|
120
|
+
actions.push({
|
|
121
|
+
kind: 'ph-init',
|
|
122
|
+
targetDir: path.dirname(result.appDir),
|
|
123
|
+
appDir: result.appDir,
|
|
124
|
+
spec,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (ei <= actionIndex('app-install')) {
|
|
128
|
+
actions.push({ kind: 'app-install', dir: result.appDir });
|
|
129
|
+
}
|
|
130
|
+
if (ei <= actionIndex('app-build')) {
|
|
131
|
+
actions.push({ kind: 'app-build', dir: result.appDir });
|
|
132
|
+
}
|
|
133
|
+
if (ei <= actionIndex('cli-install')) {
|
|
134
|
+
actions.push({ kind: 'cli-install', dir: result.cliDir });
|
|
135
|
+
}
|
|
136
|
+
if (ei <= actionIndex('cli-build')) {
|
|
137
|
+
actions.push({ kind: 'cli-build', dir: result.cliDir });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (earliest && !split) {
|
|
141
|
+
const ei = actionIndex(earliest);
|
|
142
|
+
if (ei <= actionIndex('cli-install')) {
|
|
143
|
+
actions.push({ kind: 'cli-install', dir: result.cliDir });
|
|
144
|
+
}
|
|
145
|
+
if (ei <= actionIndex('cli-build')) {
|
|
146
|
+
actions.push({ kind: 'cli-build', dir: result.cliDir });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// skills-sync is independent — append if needed
|
|
150
|
+
if (spec.externalSkills.length > 0) {
|
|
151
|
+
const targetDir = split ? result.cliDir : result.cliDir;
|
|
152
|
+
actions.push({
|
|
153
|
+
kind: 'skills-sync',
|
|
154
|
+
targetDir,
|
|
155
|
+
desired: spec.externalSkills,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return actions;
|
|
159
|
+
}
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Human-readable label for an action
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
function actionLabel(action) {
|
|
164
|
+
switch (action.kind) {
|
|
165
|
+
case 'ph-init':
|
|
166
|
+
return `Initialize reactor package (${path.basename(action.appDir)})`;
|
|
167
|
+
case 'app-install':
|
|
168
|
+
return `Install dependencies (${path.basename(action.dir)})`;
|
|
169
|
+
case 'app-build':
|
|
170
|
+
return `Build (${path.basename(action.dir)})`;
|
|
171
|
+
case 'cli-install':
|
|
172
|
+
return `Install dependencies (${path.basename(action.dir)})`;
|
|
173
|
+
case 'cli-build':
|
|
174
|
+
return `Build (${path.basename(action.dir)})`;
|
|
175
|
+
case 'skills-sync':
|
|
176
|
+
return `Sync external skills (${action.desired.length} skill${action.desired.length === 1 ? '' : 's'})`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// runPostGenActions — effectful runner
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
export async function runPostGenActions(actions, ctx) {
|
|
183
|
+
if (actions.length === 0)
|
|
184
|
+
return [];
|
|
185
|
+
const skip = ctx.skip ?? new Set();
|
|
186
|
+
// Filter out explicitly skipped actions. Also skip builds if their
|
|
187
|
+
// corresponding install is skipped (building without install is pointless).
|
|
188
|
+
const effective = actions.filter((a) => {
|
|
189
|
+
if (skip.has(a.kind))
|
|
190
|
+
return false;
|
|
191
|
+
// If app-install is skipped, also skip app-build
|
|
192
|
+
if (a.kind === 'app-build' && skip.has('app-install'))
|
|
193
|
+
return false;
|
|
194
|
+
// If cli-install is skipped, also skip cli-build
|
|
195
|
+
if (a.kind === 'cli-build' && skip.has('cli-install'))
|
|
196
|
+
return false;
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
if (effective.length === 0)
|
|
200
|
+
return [];
|
|
201
|
+
// Print numbered summary
|
|
202
|
+
ctx.log(`\nPost-generation steps (${effective.length}):`);
|
|
203
|
+
effective.forEach((a, i) => {
|
|
204
|
+
ctx.log(` ${i + 1}. ${actionLabel(a)}`);
|
|
205
|
+
});
|
|
206
|
+
ctx.log('');
|
|
207
|
+
const results = [];
|
|
208
|
+
const failed = new Set();
|
|
209
|
+
for (let i = 0; i < effective.length; i++) {
|
|
210
|
+
const action = effective[i];
|
|
211
|
+
const label = actionLabel(action);
|
|
212
|
+
const prefix = `[${i + 1}/${effective.length}]`;
|
|
213
|
+
// Check if a dependency failed
|
|
214
|
+
const failedDep = [...failed].find((f) => dependsOn(action.kind, f));
|
|
215
|
+
if (failedDep) {
|
|
216
|
+
ctx.log(`${prefix} ${label}... skipped (dependency failed)`);
|
|
217
|
+
results.push({
|
|
218
|
+
action,
|
|
219
|
+
status: 'skipped',
|
|
220
|
+
reason: 'dependency failed',
|
|
221
|
+
durationMs: 0,
|
|
222
|
+
});
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
ctx.log(`${prefix} ${label}...`);
|
|
226
|
+
const t0 = Date.now();
|
|
227
|
+
try {
|
|
228
|
+
const success = await executeAction(action, ctx);
|
|
229
|
+
const elapsed = Date.now() - t0;
|
|
230
|
+
if (success) {
|
|
231
|
+
ctx.log(` done (${formatDuration(elapsed)})`);
|
|
232
|
+
results.push({ action, status: 'success', durationMs: elapsed });
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
ctx.log(` FAILED`);
|
|
236
|
+
results.push({ action, status: 'failed', durationMs: elapsed });
|
|
237
|
+
failed.add(action.kind);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
const elapsed = Date.now() - t0;
|
|
242
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
243
|
+
ctx.log(` FAILED: ${msg}`);
|
|
244
|
+
results.push({
|
|
245
|
+
action,
|
|
246
|
+
status: 'failed',
|
|
247
|
+
reason: msg,
|
|
248
|
+
durationMs: elapsed,
|
|
249
|
+
});
|
|
250
|
+
failed.add(action.kind);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Print summary
|
|
254
|
+
const succeeded = results.filter((r) => r.status === 'success').length;
|
|
255
|
+
const failedCount = results.filter((r) => r.status === 'failed').length;
|
|
256
|
+
const skippedCount = results.filter((r) => r.status === 'skipped').length;
|
|
257
|
+
ctx.log('');
|
|
258
|
+
if (failedCount === 0 && skippedCount === 0) {
|
|
259
|
+
ctx.log(`Post-generation: ${succeeded} step${succeeded === 1 ? '' : 's'} completed successfully.`);
|
|
260
|
+
ctx.log('Ready — run `pnpm dev` to start.');
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
ctx.log(`Post-generation: ${succeeded} succeeded, ${failedCount} failed, ${skippedCount} skipped.`);
|
|
264
|
+
if (failedCount > 0) {
|
|
265
|
+
ctx.log('Fix the reported error(s) and re-run `ph-clint clint-project-regen`.');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return results;
|
|
269
|
+
}
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Action dispatch
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
async function executeAction(action, ctx) {
|
|
274
|
+
switch (action.kind) {
|
|
275
|
+
case 'ph-init': {
|
|
276
|
+
const { runPhInit } = await import('./scaffold.js');
|
|
277
|
+
const result = await runPhInit({
|
|
278
|
+
targetDir: action.targetDir,
|
|
279
|
+
appDir: action.appDir,
|
|
280
|
+
spec: action.spec,
|
|
281
|
+
log: (msg) => ctx.log(` ${msg}`),
|
|
282
|
+
runProcess: ctx.runProcess,
|
|
283
|
+
});
|
|
284
|
+
if (!result.ran) {
|
|
285
|
+
ctx.log(` ${result.reason ?? 'ph init did not run'}`);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
return result.exitCode === 0;
|
|
289
|
+
}
|
|
290
|
+
case 'app-install':
|
|
291
|
+
case 'cli-install': {
|
|
292
|
+
if (ctx.runProcess) {
|
|
293
|
+
const result = await ctx.runProcess('pnpm install', {
|
|
294
|
+
cwd: action.dir,
|
|
295
|
+
timeout: 300_000,
|
|
296
|
+
});
|
|
297
|
+
return result.success;
|
|
298
|
+
}
|
|
299
|
+
// Fallback: direct spawn
|
|
300
|
+
const { runCommand } = await import('./exec.js');
|
|
301
|
+
const result = await runCommand('pnpm', ['install'], {
|
|
302
|
+
cwd: action.dir,
|
|
303
|
+
stdio: 'inherit',
|
|
304
|
+
});
|
|
305
|
+
return result.exitCode === 0;
|
|
306
|
+
}
|
|
307
|
+
case 'app-build':
|
|
308
|
+
case 'cli-build': {
|
|
309
|
+
return runBuild(action.dir, ctx);
|
|
310
|
+
}
|
|
311
|
+
case 'skills-sync': {
|
|
312
|
+
const { syncExternalSkills } = await import('../skills/sync.js');
|
|
313
|
+
await syncExternalSkills({
|
|
314
|
+
targetDir: action.targetDir,
|
|
315
|
+
desired: action.desired,
|
|
316
|
+
log: (msg) => ctx.log(` ${msg}`),
|
|
317
|
+
});
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function runBuild(dir, ctx) {
|
|
323
|
+
if (ctx.runProcess) {
|
|
324
|
+
const result = await ctx.runProcess('pnpm build', {
|
|
325
|
+
cwd: dir,
|
|
326
|
+
timeout: 120_000,
|
|
327
|
+
});
|
|
328
|
+
return result.success;
|
|
329
|
+
}
|
|
330
|
+
// Fallback: direct spawn
|
|
331
|
+
const { runCommand } = await import('./exec.js');
|
|
332
|
+
const result = await runCommand('pnpm', ['build'], {
|
|
333
|
+
cwd: dir,
|
|
334
|
+
stdio: 'inherit',
|
|
335
|
+
});
|
|
336
|
+
return result.exitCode === 0;
|
|
337
|
+
}
|
|
338
|
+
function formatDuration(ms) {
|
|
339
|
+
if (ms < 1000)
|
|
340
|
+
return `${ms}ms`;
|
|
341
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
342
|
+
}
|
|
@@ -72,7 +72,8 @@ export function buildCliPackageJson(spec) {
|
|
|
72
72
|
}
|
|
73
73
|
const scripts = {
|
|
74
74
|
'build:skills': 'tsx scripts/build-skills.ts',
|
|
75
|
-
build:
|
|
75
|
+
'build:manifest': 'build-manifest',
|
|
76
|
+
build: 'pnpm build:skills && tsc && pnpm build:manifest',
|
|
76
77
|
dev: 'tsx src/main.ts',
|
|
77
78
|
start: 'node dist/main.js',
|
|
78
79
|
test: "NODE_OPTIONS='--experimental-vm-modules' jest --detectOpenHandles",
|
|
@@ -86,9 +87,12 @@ export function buildCliPackageJson(spec) {
|
|
|
86
87
|
// Single-layout: publish scripts live in the CLI package.json (which IS the
|
|
87
88
|
// root). Split-layout gets these from root-package-json.ts instead.
|
|
88
89
|
if (!phAtLeast(powerhouse, 'Reactor')) {
|
|
89
|
-
scripts['publish:dev'] = 'ph-publish dev -c ./publish.config.
|
|
90
|
-
scripts['publish:staging'] = 'ph-publish staging -c ./publish.config.
|
|
91
|
-
scripts['publish:production'] = 'ph-publish production -c ./publish.config.
|
|
90
|
+
scripts['publish:dev'] = 'ph-publish dev -c ./publish.config.js';
|
|
91
|
+
scripts['publish:staging'] = 'ph-publish staging -c ./publish.config.js';
|
|
92
|
+
scripts['publish:production'] = 'ph-publish production -c ./publish.config.js';
|
|
93
|
+
}
|
|
94
|
+
if (spec.deployment.serviceAnnouncement) {
|
|
95
|
+
scripts['test-service-registry'] = 'vetra-graphql-registry --withAuth';
|
|
92
96
|
}
|
|
93
97
|
const pkg = {
|
|
94
98
|
name: pkgName,
|
|
@@ -46,6 +46,6 @@ export declare function getProfileFileBuilders(spec: ClintProjectSpec): FileBuil
|
|
|
46
46
|
export declare const CLI_FILE_BUILDERS: FileBuilder[];
|
|
47
47
|
export { buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildDemoAgentTs, buildFrameworkGenTs, buildFrameworkTs, buildManifestJson, };
|
|
48
48
|
export { buildRootPackageJson } from './root-package-json.js';
|
|
49
|
-
export {
|
|
49
|
+
export { buildPublishConfigJs } from './publish-config-js.js';
|
|
50
50
|
export { buildReadme } from './readme-md.js';
|
|
51
51
|
export { buildAppIndexTs } from './app-index-ts.js';
|
|
@@ -79,6 +79,6 @@ export const CLI_FILE_BUILDERS = [
|
|
|
79
79
|
];
|
|
80
80
|
export { buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildDemoAgentTs, buildFrameworkGenTs, buildFrameworkTs, buildManifestJson, };
|
|
81
81
|
export { buildRootPackageJson } from './root-package-json.js';
|
|
82
|
-
export {
|
|
82
|
+
export { buildPublishConfigJs } from './publish-config-js.js';
|
|
83
83
|
export { buildReadme } from './readme-md.js';
|
|
84
84
|
export { buildAppIndexTs } from './app-index-ts.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the root `publish.config.js`.
|
|
3
|
+
*
|
|
4
|
+
* Split-layout: a lockstep group with both app and cli packages.
|
|
5
|
+
* Single-layout: a single-package group at `.`.
|
|
6
|
+
*/
|
|
7
|
+
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
8
|
+
export declare function buildPublishConfigJs(spec: ClintProjectSpec): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the root `publish.config.js`.
|
|
3
|
+
*
|
|
4
|
+
* Split-layout: a lockstep group with both app and cli packages.
|
|
5
|
+
* Single-layout: a single-package group at `.`.
|
|
6
|
+
*/
|
|
7
|
+
import { phAtLeast } from '../../spec/types.js';
|
|
8
|
+
export function buildPublishConfigJs(spec) {
|
|
9
|
+
const split = phAtLeast(spec.features.powerhouse, 'Reactor');
|
|
10
|
+
// The publish pipeline appends the prerelease suffix (e.g. -dev.0), so we
|
|
11
|
+
// only store the base semver (M.m.p) here.
|
|
12
|
+
const baseVersion = spec.version.replace(/-.*$/, '');
|
|
13
|
+
const packages = split
|
|
14
|
+
? `[
|
|
15
|
+
{ path: '${spec.name}-app', category: 'app' },
|
|
16
|
+
{ path: '${spec.name}-cli', category: 'cli' },
|
|
17
|
+
]`
|
|
18
|
+
: `[
|
|
19
|
+
{ path: '.', category: 'cli' },
|
|
20
|
+
]`;
|
|
21
|
+
return `// Publish config — loaded by ph-clint's publish pipeline.
|
|
22
|
+
// definePublishConfig is an identity function; a plain export is equivalent.
|
|
23
|
+
export default {
|
|
24
|
+
groups: {
|
|
25
|
+
'${spec.name}': {
|
|
26
|
+
version: '${baseVersion}',
|
|
27
|
+
packages: ${packages},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
@@ -25,14 +25,18 @@ export function buildRootPackageJson(spec) {
|
|
|
25
25
|
lint: prefix(cli, 'lint'),
|
|
26
26
|
'app:dev': prefix(app, 'dev'),
|
|
27
27
|
'cli:dev': prefix(cli, 'dev'),
|
|
28
|
-
'publish:dev': `${prefix(cli, 'exec ph-publish')} dev -c ../publish.config.
|
|
29
|
-
'publish:staging': `${prefix(cli, 'exec ph-publish')} staging -c ../publish.config.
|
|
30
|
-
'publish:production': `${prefix(cli, 'exec ph-publish')} production -c ../publish.config.
|
|
28
|
+
'publish:dev': `${prefix(cli, 'exec ph-publish')} dev -c ../publish.config.js`,
|
|
29
|
+
'publish:staging': `${prefix(cli, 'exec ph-publish')} staging -c ../publish.config.js`,
|
|
30
|
+
'publish:production': `${prefix(cli, 'exec ph-publish')} production -c ../publish.config.js`,
|
|
31
31
|
};
|
|
32
|
+
if (spec.deployment.serviceAnnouncement) {
|
|
33
|
+
scripts['test-service-registry'] = prefix(cli, 'test-service-registry');
|
|
34
|
+
}
|
|
32
35
|
const pkg = {
|
|
33
36
|
name: getPackageName(spec),
|
|
34
37
|
version: spec.version,
|
|
35
38
|
private: true,
|
|
39
|
+
type: 'module',
|
|
36
40
|
...(spec.description ? { description: spec.description } : {}),
|
|
37
41
|
scripts,
|
|
38
42
|
};
|
package/dist/codegen/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ClintProjectSpec } from '../spec/types.js';
|
|
2
|
+
import { type PostGenAction } from './actions.js';
|
|
2
3
|
export type GenerateMode = 'create' | 'update' | 'auto';
|
|
3
4
|
export interface GenerateProjectOptions {
|
|
4
5
|
/** Absolute path to the project root. */
|
|
@@ -32,6 +33,8 @@ export interface GenerateProjectResult {
|
|
|
32
33
|
migrated: boolean;
|
|
33
34
|
cliDir: string;
|
|
34
35
|
appDir: string | null;
|
|
36
|
+
/** Pending post-generation actions derived from what changed. */
|
|
37
|
+
pendingActions: PostGenAction[];
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
37
40
|
* Generate / reconcile a project at `targetDir` from the given spec.
|
package/dist/codegen/index.js
CHANGED
|
@@ -35,7 +35,8 @@ import { hashContent, hashFile, readHashes, writeHashes, } from './hashes.js';
|
|
|
35
35
|
import { hasMarkers, spliceMarkerRegions } from './markers.js';
|
|
36
36
|
import { writeFileEnsuringDir, isDirEmptyEnough } from './write.js';
|
|
37
37
|
import { migrateFlatToSplit } from './migrate/flat-to-split.js';
|
|
38
|
-
import { CLI_FILE_BUILDERS, getProfileFileBuilders, buildReadme, buildRootPackageJson,
|
|
38
|
+
import { CLI_FILE_BUILDERS, getProfileFileBuilders, buildReadme, buildRootPackageJson, buildPublishConfigJs, buildAppIndexTs, } from './builders/index.js';
|
|
39
|
+
import { collectPostGenActions, } from './actions.js';
|
|
39
40
|
/**
|
|
40
41
|
* Generate / reconcile a project at `targetDir` from the given spec.
|
|
41
42
|
*/
|
|
@@ -100,11 +101,11 @@ function planFiles(spec, targetDir) {
|
|
|
100
101
|
content: buildReadme(spec),
|
|
101
102
|
});
|
|
102
103
|
// Publish config — always at the project root (both layouts).
|
|
103
|
-
const publishConfig = path.join(targetDir, 'publish.config.
|
|
104
|
+
const publishConfig = path.join(targetDir, 'publish.config.js');
|
|
104
105
|
planned.push({
|
|
105
|
-
relativePath: 'publish.config.
|
|
106
|
+
relativePath: 'publish.config.js',
|
|
106
107
|
absolutePath: publishConfig,
|
|
107
|
-
content:
|
|
108
|
+
content: buildPublishConfigJs(spec),
|
|
108
109
|
});
|
|
109
110
|
// Split-layout only: root package.json + app placeholder files.
|
|
110
111
|
if (split && appDir) {
|
|
@@ -178,7 +179,7 @@ async function runCreate(options, _warn) {
|
|
|
178
179
|
relativePath: path.relative(targetDir, getSpecPath(targetDir)),
|
|
179
180
|
});
|
|
180
181
|
await writeHashes(targetDir, hashes);
|
|
181
|
-
|
|
182
|
+
const result = {
|
|
182
183
|
mode: 'create',
|
|
183
184
|
files,
|
|
184
185
|
skipped: [],
|
|
@@ -186,7 +187,10 @@ async function runCreate(options, _warn) {
|
|
|
186
187
|
migrated: false,
|
|
187
188
|
cliDir,
|
|
188
189
|
appDir,
|
|
190
|
+
pendingActions: [],
|
|
189
191
|
};
|
|
192
|
+
result.pendingActions = collectPostGenActions(result, spec);
|
|
193
|
+
return result;
|
|
190
194
|
}
|
|
191
195
|
/** Update mode — reconcile against an existing project. */
|
|
192
196
|
async function runUpdate(options, warn, migrated) {
|
|
@@ -295,7 +299,7 @@ async function runUpdate(options, warn, migrated) {
|
|
|
295
299
|
}
|
|
296
300
|
await writeProjectSpec(targetDir, spec);
|
|
297
301
|
await writeHashes(targetDir, next);
|
|
298
|
-
|
|
302
|
+
const result = {
|
|
299
303
|
mode: 'update',
|
|
300
304
|
files,
|
|
301
305
|
skipped,
|
|
@@ -303,5 +307,8 @@ async function runUpdate(options, warn, migrated) {
|
|
|
303
307
|
migrated,
|
|
304
308
|
cliDir,
|
|
305
309
|
appDir,
|
|
310
|
+
pendingActions: [],
|
|
306
311
|
};
|
|
312
|
+
result.pendingActions = collectPostGenActions(result, spec);
|
|
313
|
+
return result;
|
|
307
314
|
}
|
|
@@ -18,7 +18,7 @@ import { z } from 'zod';
|
|
|
18
18
|
import { clintProjectSpecSchema, } from '../spec/types.js';
|
|
19
19
|
import { generateProject } from '../codegen/index.js';
|
|
20
20
|
import { isDirEmptyEnough } from '../codegen/write.js';
|
|
21
|
-
import {
|
|
21
|
+
import { runPostGenActions } from '../codegen/actions.js';
|
|
22
22
|
import { writeProjectSpec } from '../spec/file.js';
|
|
23
23
|
import { specToImportInput } from '../triggers/spec-change.js';
|
|
24
24
|
const inputSchema = z.object({
|
|
@@ -143,37 +143,20 @@ export const init = defineCommand({
|
|
|
143
143
|
stdout(`Note: could not create spec document — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
log: (msg) => stdout(msg + '\n'),
|
|
154
|
-
runProcess,
|
|
155
|
-
});
|
|
156
|
-
if (!phResult.ran) {
|
|
157
|
-
stdout(`Note: \`ph init\` did not run — ${phResult.reason ?? 'see above'}.\n`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// Dependency installation.
|
|
161
|
-
if (!input.skipInstall) {
|
|
162
|
-
const dirs = [];
|
|
163
|
-
if (split) {
|
|
164
|
-
if (result.appDir)
|
|
165
|
-
dirs.push(result.appDir);
|
|
166
|
-
dirs.push(result.cliDir);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
dirs.push(targetDir);
|
|
170
|
-
}
|
|
171
|
-
await runPnpmInstall({
|
|
172
|
-
dirs,
|
|
173
|
-
log: (msg) => stdout(msg + '\n'),
|
|
174
|
-
runProcess,
|
|
175
|
-
});
|
|
146
|
+
// Run post-generation actions (ph-init, install, build, skills-sync).
|
|
147
|
+
const skip = new Set();
|
|
148
|
+
if (input.skipPhInit)
|
|
149
|
+
skip.add('ph-init');
|
|
150
|
+
if (input.skipInstall) {
|
|
151
|
+
skip.add('app-install');
|
|
152
|
+
skip.add('cli-install');
|
|
176
153
|
}
|
|
154
|
+
await runPostGenActions(result.pendingActions, {
|
|
155
|
+
log: (msg) => stdout(msg + '\n'),
|
|
156
|
+
runProcess,
|
|
157
|
+
skip,
|
|
158
|
+
});
|
|
159
|
+
const split = spec.features.powerhouse !== 'Disabled';
|
|
177
160
|
const next = [];
|
|
178
161
|
next.push('');
|
|
179
162
|
next.push('Next steps:');
|
|
@@ -36,7 +36,7 @@ export const clintProjectPublish = defineCommand({
|
|
|
36
36
|
stdout(msg + '\n');
|
|
37
37
|
};
|
|
38
38
|
// 1. Resolve plan — discover config from the target dir, not process.cwd().
|
|
39
|
-
const configFile = path.join(dir, 'publish.config.
|
|
39
|
+
const configFile = path.join(dir, 'publish.config.js');
|
|
40
40
|
const plan = await resolvePublishPlan({
|
|
41
41
|
tag: input.tag,
|
|
42
42
|
configPath: configFile,
|
|
@@ -11,6 +11,7 @@ import { defineCommand } from '@powerhousedao/ph-clint';
|
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import { readProjectSpec } from '../spec/file.js';
|
|
13
13
|
import { generateProject } from '../codegen/index.js';
|
|
14
|
+
import { runPostGenActions } from '../codegen/actions.js';
|
|
14
15
|
const inputSchema = z.object({
|
|
15
16
|
dir: z
|
|
16
17
|
.string()
|
|
@@ -25,7 +26,7 @@ export const regen = defineCommand({
|
|
|
25
26
|
id: 'clint-project-regen',
|
|
26
27
|
description: 'Regenerate project files from the persisted spec',
|
|
27
28
|
inputSchema,
|
|
28
|
-
execute: async (input, { workdir, stdout, log }) => {
|
|
29
|
+
execute: async (input, { workdir, stdout, log, runProcess }) => {
|
|
29
30
|
const targetDir = path.resolve(workdir, input.dir ?? '.');
|
|
30
31
|
const spec = await readProjectSpec(targetDir);
|
|
31
32
|
if (!spec) {
|
|
@@ -53,6 +54,11 @@ export const regen = defineCommand({
|
|
|
53
54
|
stdout('warning: ' + w + '\n');
|
|
54
55
|
}
|
|
55
56
|
stdout(lines.join('\n') + '\n');
|
|
57
|
+
// Run post-generation actions (install, build, skills-sync).
|
|
58
|
+
await runPostGenActions(result.pendingActions, {
|
|
59
|
+
log: (msg) => stdout(msg + '\n'),
|
|
60
|
+
runProcess,
|
|
61
|
+
});
|
|
56
62
|
return {
|
|
57
63
|
text: lines.join('\n'),
|
|
58
64
|
data: {
|
|
@@ -20,10 +20,9 @@ import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
|
|
|
20
20
|
import { importSpec } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
|
|
21
21
|
import { createDocumentChangeTrigger } from '../framework.js';
|
|
22
22
|
import { generateProject } from '../codegen/index.js';
|
|
23
|
-
import {
|
|
23
|
+
import { runPostGenActions } from '../codegen/actions.js';
|
|
24
24
|
import { specFromDocumentState } from '../spec/from-document.js';
|
|
25
25
|
import { readProjectSpec, writeProjectSpec } from '../spec/file.js';
|
|
26
|
-
import { syncExternalSkills } from '../skills/sync.js';
|
|
27
26
|
import { clintProject } from '../services/clint-project.js';
|
|
28
27
|
const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
|
|
29
28
|
const HASH_DIR = path.join('.ph', 'ph-clint-cli');
|
|
@@ -184,62 +183,11 @@ export const specChangeTrigger = createDocumentChangeTrigger({
|
|
|
184
183
|
for (const w of warnings)
|
|
185
184
|
log.warn(`${TAG} ${w}`);
|
|
186
185
|
}
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await fs.access(appPkgJson);
|
|
193
|
-
appExists = true;
|
|
194
|
-
}
|
|
195
|
-
catch { /* noop */ }
|
|
196
|
-
if (!appExists) {
|
|
197
|
-
log?.info(`${TAG} initializing reactor package at ${result.appDir}`);
|
|
198
|
-
const phResult = await runPhInit({
|
|
199
|
-
targetDir,
|
|
200
|
-
appDir: result.appDir,
|
|
201
|
-
spec,
|
|
202
|
-
log: (msg) => log?.info(`${TAG} ${msg}`),
|
|
203
|
-
runProcess: ctx.context.runProcess,
|
|
204
|
-
});
|
|
205
|
-
if (!phResult.ran) {
|
|
206
|
-
log?.warn(`${TAG} ph init skipped: ${phResult.reason ?? 'unknown'}`);
|
|
207
|
-
}
|
|
208
|
-
else if (phResult.exitCode !== 0) {
|
|
209
|
-
log?.warn(`${TAG} ph init exited with code ${phResult.exitCode}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
// Always ensure the app package name matches the spec's scope,
|
|
213
|
-
// even if ph init ran in a previous trigger cycle without scope.
|
|
214
|
-
if (spec.scope) {
|
|
215
|
-
try {
|
|
216
|
-
const raw = await fs.readFile(appPkgJson, 'utf8');
|
|
217
|
-
const pkg = JSON.parse(raw);
|
|
218
|
-
const appFolder = path.basename(result.appDir);
|
|
219
|
-
const scopedName = `@${spec.scope}/${appFolder}`;
|
|
220
|
-
if (pkg.name !== scopedName) {
|
|
221
|
-
pkg.name = scopedName;
|
|
222
|
-
pkg.publishConfig = { access: 'public' };
|
|
223
|
-
await fs.writeFile(appPkgJson, JSON.stringify(pkg, null, 2) + '\n');
|
|
224
|
-
log?.info(`${TAG} patched app package name to ${scopedName}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
log?.warn(`${TAG} could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// Sync external skills after codegen.
|
|
233
|
-
if (spec.externalSkills.length > 0) {
|
|
234
|
-
const skillsResult = await syncExternalSkills({
|
|
235
|
-
targetDir,
|
|
236
|
-
desired: spec.externalSkills,
|
|
237
|
-
log: (msg) => log?.info(`${TAG} ${msg}`),
|
|
238
|
-
});
|
|
239
|
-
if (log && (skillsResult.added.length > 0 || skillsResult.removed.length > 0)) {
|
|
240
|
-
log.info(`${TAG} skills sync: +${skillsResult.added.length} -${skillsResult.removed.length}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
186
|
+
// Run post-generation actions (ph-init, install, build, skills-sync).
|
|
187
|
+
await runPostGenActions(result.pendingActions, {
|
|
188
|
+
log: (msg) => log?.info(`${TAG} ${msg}`),
|
|
189
|
+
runProcess: ctx.context.runProcess,
|
|
190
|
+
});
|
|
243
191
|
}
|
|
244
192
|
// Work done inline, no additional work item needed
|
|
245
193
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/ph-clint-cli",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.34",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"@mastra/core": "^1.22.0",
|
|
30
30
|
"@mastra/libsql": "^1.7.4",
|
|
31
31
|
"@mastra/memory": "^1.13.1",
|
|
32
|
-
"@powerhousedao/ph-clint": "^0.1.0-dev.
|
|
33
|
-
"@powerhousedao/ph-clint-app": "^0.1.0-dev.
|
|
34
|
-
"@powerhousedao/ph-clint-dev": "^0.1.0-dev.
|
|
32
|
+
"@powerhousedao/ph-clint": "^0.1.0-dev.34",
|
|
33
|
+
"@powerhousedao/ph-clint-app": "^0.1.0-dev.34",
|
|
34
|
+
"@powerhousedao/ph-clint-dev": "^0.1.0-dev.34",
|
|
35
35
|
"@powerhousedao/reactor": "6.0.0-dev.170",
|
|
36
36
|
"@powerhousedao/reactor-api": "6.0.0-dev.170",
|
|
37
37
|
"@powerhousedao/shared": "6.0.0-dev.170",
|