pi-gsd 2.0.0 → 2.0.1
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/package.json +2 -1
- package/src/cli.ts +644 -0
- package/src/commands/base.ts +67 -0
- package/src/commands/commit.ts +22 -0
- package/src/commands/config.ts +71 -0
- package/src/commands/frontmatter.ts +51 -0
- package/src/commands/index.ts +76 -0
- package/src/commands/init.ts +43 -0
- package/src/commands/milestone.ts +37 -0
- package/src/commands/phase.ts +92 -0
- package/src/commands/progress.ts +71 -0
- package/src/commands/roadmap.ts +40 -0
- package/src/commands/scaffold.ts +19 -0
- package/src/commands/state.ts +102 -0
- package/src/commands/template.ts +52 -0
- package/src/commands/verify.ts +70 -0
- package/src/commands/workstream.ts +98 -0
- package/src/commands/wxp.ts +65 -0
- package/src/lib/commands.ts +1040 -0
- package/src/lib/config.ts +385 -0
- package/src/lib/core.ts +1167 -0
- package/src/lib/frontmatter.ts +462 -0
- package/src/lib/init.ts +517 -0
- package/src/lib/milestone.ts +290 -0
- package/src/lib/model-profiles.ts +272 -0
- package/src/lib/phase.ts +1012 -0
- package/src/lib/profile-output.ts +237 -0
- package/src/lib/profile-pipeline.ts +556 -0
- package/src/lib/roadmap.ts +378 -0
- package/src/lib/schemas.ts +290 -0
- package/src/lib/security.ts +176 -0
- package/src/lib/state.ts +1175 -0
- package/src/lib/template.ts +246 -0
- package/src/lib/uat.ts +289 -0
- package/src/lib/verify.ts +879 -0
- package/src/lib/workstream.ts +524 -0
- package/src/output.ts +45 -0
- package/src/schemas/pi-gsd-settings.schema.json +80 -0
- package/src/schemas/wxp.xsd +619 -0
- package/src/schemas/wxp.zod.ts +318 -0
- package/src/wxp/__tests__/arguments.test.ts +86 -0
- package/src/wxp/__tests__/conditions.test.ts +106 -0
- package/src/wxp/__tests__/executor.test.ts +95 -0
- package/src/wxp/__tests__/helpers.ts +26 -0
- package/src/wxp/__tests__/integration.test.ts +166 -0
- package/src/wxp/__tests__/new-features.test.ts +222 -0
- package/src/wxp/__tests__/parser.test.ts +159 -0
- package/src/wxp/__tests__/paste.test.ts +66 -0
- package/src/wxp/__tests__/schema.test.ts +120 -0
- package/src/wxp/__tests__/security.test.ts +87 -0
- package/src/wxp/__tests__/shell.test.ts +85 -0
- package/src/wxp/__tests__/string-ops.test.ts +25 -0
- package/src/wxp/__tests__/variables.test.ts +65 -0
- package/src/wxp/arguments.ts +89 -0
- package/src/wxp/conditions.ts +78 -0
- package/src/wxp/executor.ts +191 -0
- package/src/wxp/index.ts +191 -0
- package/src/wxp/parser.ts +198 -0
- package/src/wxp/paste.ts +51 -0
- package/src/wxp/security.ts +102 -0
- package/src/wxp/shell.ts +81 -0
- package/src/wxp/string-ops.ts +44 -0
- package/src/wxp/variables.ts +109 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-gsd",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Get Shit Done - Unofficial port of the renowned AI-native project-planning spec-driven toolkit",
|
|
5
5
|
"main": "dist/pi-gsd-tools.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
|
+
"src",
|
|
24
25
|
"scripts/postinstall.js",
|
|
25
26
|
".gsd/extensions",
|
|
26
27
|
".gsd/harnesses",
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli.ts - GSD Tools CLI entry point.
|
|
4
|
+
*
|
|
5
|
+
* Oclif-based command router (CLI-01, CLI-02, CLI-03).
|
|
6
|
+
* All commands are typed oclif classes in src/commands/.
|
|
7
|
+
* Commander.js has been removed (CLI-05).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import {
|
|
13
|
+
findProjectRoot,
|
|
14
|
+
getActiveWorkstream,
|
|
15
|
+
gsdError,
|
|
16
|
+
resolveWorktreeRoot,
|
|
17
|
+
} from "./lib/core.js";
|
|
18
|
+
import { formatOutput, type OutputFormat } from "./output.js";
|
|
19
|
+
import type { Command } from "@oclif/core";
|
|
20
|
+
|
|
21
|
+
// ─── Oclif command map (CLI-01, CLI-02, CLI-03) ───────────────────────────────
|
|
22
|
+
|
|
23
|
+
type CommandConstructor = typeof Command & { run(argv: string[]): Promise<void> };
|
|
24
|
+
|
|
25
|
+
async function buildCommandMap(): Promise<Record<string, CommandConstructor>> {
|
|
26
|
+
const {
|
|
27
|
+
StateJsonCommand,
|
|
28
|
+
StateGetCommand,
|
|
29
|
+
StateUpdateCommand,
|
|
30
|
+
StatePatchCommand,
|
|
31
|
+
StateAdvancePlanCommand,
|
|
32
|
+
StateLoadCommand,
|
|
33
|
+
StateUpdateProgressCommand,
|
|
34
|
+
InitCommand,
|
|
35
|
+
RoadmapAnalyzeCommand,
|
|
36
|
+
RoadmapGetPhaseCommand,
|
|
37
|
+
RoadmapUpdatePlanProgressCommand,
|
|
38
|
+
ConfigGetCommand,
|
|
39
|
+
ConfigSetCommand,
|
|
40
|
+
ConfigSetModelProfileCommand,
|
|
41
|
+
ConfigNewProjectCommand,
|
|
42
|
+
ConfigEnsureSectionCommand,
|
|
43
|
+
PhaseNextDecimalCommand,
|
|
44
|
+
PhaseAddCommand,
|
|
45
|
+
PhaseInsertCommand,
|
|
46
|
+
PhaseRemoveCommand,
|
|
47
|
+
PhaseCompleteCommand,
|
|
48
|
+
PhasePlanIndexCommand,
|
|
49
|
+
MilestoneCompleteCommand,
|
|
50
|
+
RequirementsMarkCompleteCommand,
|
|
51
|
+
ValidateConsistencyCommand,
|
|
52
|
+
ValidateHealthCommand,
|
|
53
|
+
ValidateAgentsCommand,
|
|
54
|
+
VerifyCommand,
|
|
55
|
+
AuditUatCommand,
|
|
56
|
+
WorkstreamCreateCommand,
|
|
57
|
+
WorkstreamListCommand,
|
|
58
|
+
WorkstreamStatusCommand,
|
|
59
|
+
WorkstreamCompleteCommand,
|
|
60
|
+
WorkstreamSetCommand,
|
|
61
|
+
WorkstreamGetCommand,
|
|
62
|
+
WorkstreamProgressCommand,
|
|
63
|
+
ScaffoldCommand,
|
|
64
|
+
CommitCommand,
|
|
65
|
+
FrontmatterGetCommand,
|
|
66
|
+
FrontmatterSetCommand,
|
|
67
|
+
FrontmatterMergeCommand,
|
|
68
|
+
TemplateSelectCommand,
|
|
69
|
+
TemplateFillCommand,
|
|
70
|
+
ProgressCommand,
|
|
71
|
+
StatsCommand,
|
|
72
|
+
TodoCompleteCommand,
|
|
73
|
+
TodoMatchPhaseCommand,
|
|
74
|
+
SummaryExtractCommand,
|
|
75
|
+
WxpProcessCommand,
|
|
76
|
+
} = await import("./commands/index.js");
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
// state
|
|
80
|
+
"state json": StateJsonCommand as unknown as CommandConstructor,
|
|
81
|
+
"state get": StateGetCommand as unknown as CommandConstructor,
|
|
82
|
+
"state update": StateUpdateCommand as unknown as CommandConstructor,
|
|
83
|
+
"state patch": StatePatchCommand as unknown as CommandConstructor,
|
|
84
|
+
"state advance-plan": StateAdvancePlanCommand as unknown as CommandConstructor,
|
|
85
|
+
"state load": StateLoadCommand as unknown as CommandConstructor,
|
|
86
|
+
"state update-progress": StateUpdateProgressCommand as unknown as CommandConstructor,
|
|
87
|
+
// init
|
|
88
|
+
"init": InitCommand as unknown as CommandConstructor,
|
|
89
|
+
// roadmap
|
|
90
|
+
"roadmap analyze": RoadmapAnalyzeCommand as unknown as CommandConstructor,
|
|
91
|
+
"roadmap get-phase": RoadmapGetPhaseCommand as unknown as CommandConstructor,
|
|
92
|
+
"roadmap update-plan-progress": RoadmapUpdatePlanProgressCommand as unknown as CommandConstructor,
|
|
93
|
+
// config
|
|
94
|
+
"config-get": ConfigGetCommand as unknown as CommandConstructor,
|
|
95
|
+
"config-set": ConfigSetCommand as unknown as CommandConstructor,
|
|
96
|
+
"config-set-model-profile": ConfigSetModelProfileCommand as unknown as CommandConstructor,
|
|
97
|
+
"config-new-project": ConfigNewProjectCommand as unknown as CommandConstructor,
|
|
98
|
+
"config-ensure-section": ConfigEnsureSectionCommand as unknown as CommandConstructor,
|
|
99
|
+
// phase
|
|
100
|
+
"phase next-decimal": PhaseNextDecimalCommand as unknown as CommandConstructor,
|
|
101
|
+
"phase add": PhaseAddCommand as unknown as CommandConstructor,
|
|
102
|
+
"phase insert": PhaseInsertCommand as unknown as CommandConstructor,
|
|
103
|
+
"phase remove": PhaseRemoveCommand as unknown as CommandConstructor,
|
|
104
|
+
"phase complete": PhaseCompleteCommand as unknown as CommandConstructor,
|
|
105
|
+
"phase-plan-index": PhasePlanIndexCommand as unknown as CommandConstructor,
|
|
106
|
+
// milestone
|
|
107
|
+
"milestone complete": MilestoneCompleteCommand as unknown as CommandConstructor,
|
|
108
|
+
"requirements mark-complete": RequirementsMarkCompleteCommand as unknown as CommandConstructor,
|
|
109
|
+
// validate / verify
|
|
110
|
+
"validate consistency": ValidateConsistencyCommand as unknown as CommandConstructor,
|
|
111
|
+
"validate health": ValidateHealthCommand as unknown as CommandConstructor,
|
|
112
|
+
"validate agents": ValidateAgentsCommand as unknown as CommandConstructor,
|
|
113
|
+
"verify": VerifyCommand as unknown as CommandConstructor,
|
|
114
|
+
"audit-uat": AuditUatCommand as unknown as CommandConstructor,
|
|
115
|
+
// workstream
|
|
116
|
+
"workstream create": WorkstreamCreateCommand as unknown as CommandConstructor,
|
|
117
|
+
"workstream list": WorkstreamListCommand as unknown as CommandConstructor,
|
|
118
|
+
"workstream status": WorkstreamStatusCommand as unknown as CommandConstructor,
|
|
119
|
+
"workstream complete": WorkstreamCompleteCommand as unknown as CommandConstructor,
|
|
120
|
+
"workstream set": WorkstreamSetCommand as unknown as CommandConstructor,
|
|
121
|
+
"workstream get": WorkstreamGetCommand as unknown as CommandConstructor,
|
|
122
|
+
"workstream progress": WorkstreamProgressCommand as unknown as CommandConstructor,
|
|
123
|
+
// scaffold
|
|
124
|
+
"scaffold": ScaffoldCommand as unknown as CommandConstructor,
|
|
125
|
+
// commit
|
|
126
|
+
"commit": CommitCommand as unknown as CommandConstructor,
|
|
127
|
+
// frontmatter
|
|
128
|
+
"frontmatter get": FrontmatterGetCommand as unknown as CommandConstructor,
|
|
129
|
+
"frontmatter set": FrontmatterSetCommand as unknown as CommandConstructor,
|
|
130
|
+
"frontmatter merge": FrontmatterMergeCommand as unknown as CommandConstructor,
|
|
131
|
+
// template
|
|
132
|
+
"template select": TemplateSelectCommand as unknown as CommandConstructor,
|
|
133
|
+
"template fill": TemplateFillCommand as unknown as CommandConstructor,
|
|
134
|
+
// progress / stats / todo
|
|
135
|
+
"progress": ProgressCommand as unknown as CommandConstructor,
|
|
136
|
+
"stats": StatsCommand as unknown as CommandConstructor,
|
|
137
|
+
"todo complete": TodoCompleteCommand as unknown as CommandConstructor,
|
|
138
|
+
"todo match-phase": TodoMatchPhaseCommand as unknown as CommandConstructor,
|
|
139
|
+
"summary-extract": SummaryExtractCommand as unknown as CommandConstructor,
|
|
140
|
+
// wxp (CLI-04)
|
|
141
|
+
"wxp process": WxpProcessCommand as unknown as CommandConstructor,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── Legacy arg parsing (for remaining switch-based commands) ─────────────────
|
|
146
|
+
|
|
147
|
+
function parseNamedArgs(
|
|
148
|
+
args: string[],
|
|
149
|
+
valueFlags: string[] = [],
|
|
150
|
+
booleanFlags: string[] = [],
|
|
151
|
+
): Record<string, string | boolean | null> {
|
|
152
|
+
const result: Record<string, string | boolean | null> = {};
|
|
153
|
+
for (const flag of valueFlags) {
|
|
154
|
+
const idx = args.indexOf(`--${flag}`);
|
|
155
|
+
result[flag] =
|
|
156
|
+
idx !== -1 && args[idx + 1] !== undefined && !args[idx + 1].startsWith("--")
|
|
157
|
+
? args[idx + 1]
|
|
158
|
+
: null;
|
|
159
|
+
}
|
|
160
|
+
for (const flag of booleanFlags) {
|
|
161
|
+
result[flag] = args.includes(`--${flag}`);
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseMultiwordArg(args: string[], flag: string): string | null {
|
|
167
|
+
const idx = args.indexOf(`--${flag}`);
|
|
168
|
+
if (idx === -1) return null;
|
|
169
|
+
const tokens: string[] = [];
|
|
170
|
+
for (let i = idx + 1; i < args.length; i++) {
|
|
171
|
+
if (args[i].startsWith("--")) break;
|
|
172
|
+
tokens.push(args[i]);
|
|
173
|
+
}
|
|
174
|
+
return tokens.length > 0 ? tokens.join(" ") : null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function extractField(obj: unknown, fieldPath: string): unknown {
|
|
178
|
+
const parts = fieldPath.split(".");
|
|
179
|
+
let current: unknown = obj;
|
|
180
|
+
for (const part of parts) {
|
|
181
|
+
if (current === null || current === undefined) return undefined;
|
|
182
|
+
const bracketMatch = part.match(/^(.+?)\[(-?\d+)]$/);
|
|
183
|
+
if (bracketMatch) {
|
|
184
|
+
const key = bracketMatch[1];
|
|
185
|
+
const index = parseInt(bracketMatch[2], 10);
|
|
186
|
+
current = (current as Record<string, unknown>)[key];
|
|
187
|
+
if (!Array.isArray(current)) return undefined;
|
|
188
|
+
current = index < 0 ? current[current.length + index] : current[index];
|
|
189
|
+
} else {
|
|
190
|
+
current = (current as Record<string, unknown>)[part];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return current;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── Command key resolution ───────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
function findCommandKey(
|
|
199
|
+
argv: string[],
|
|
200
|
+
map: Record<string, CommandConstructor>,
|
|
201
|
+
): string | null {
|
|
202
|
+
// Try 2-token key first (e.g. "state json"), then 1-token (e.g. "commit")
|
|
203
|
+
const twoToken = argv.slice(0, 2).join(" ");
|
|
204
|
+
if (map[twoToken]) return twoToken;
|
|
205
|
+
const oneToken = argv[0];
|
|
206
|
+
if (oneToken && map[oneToken]) return oneToken;
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
async function main(): Promise<void> {
|
|
213
|
+
const argv = process.argv.slice(2);
|
|
214
|
+
|
|
215
|
+
// --help at top level
|
|
216
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
217
|
+
printHelp();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Strip --cwd
|
|
222
|
+
let cwd = process.cwd();
|
|
223
|
+
{
|
|
224
|
+
const cwdEqArg = argv.find((a) => a.startsWith("--cwd="));
|
|
225
|
+
const cwdIdx = argv.indexOf("--cwd");
|
|
226
|
+
if (cwdEqArg) {
|
|
227
|
+
const value = cwdEqArg.slice("--cwd=".length).trim();
|
|
228
|
+
if (!value) gsdError("Missing value for --cwd");
|
|
229
|
+
argv.splice(argv.indexOf(cwdEqArg), 1);
|
|
230
|
+
cwd = path.resolve(value);
|
|
231
|
+
} else if (cwdIdx !== -1) {
|
|
232
|
+
const value = argv[cwdIdx + 1];
|
|
233
|
+
if (!value || value.startsWith("--")) gsdError("Missing value for --cwd");
|
|
234
|
+
argv.splice(cwdIdx, 2);
|
|
235
|
+
cwd = path.resolve(value);
|
|
236
|
+
}
|
|
237
|
+
if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory())
|
|
238
|
+
gsdError(`Invalid --cwd: ${cwd}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Worktree resolution
|
|
242
|
+
if (!fs.existsSync(path.join(cwd, ".planning"))) {
|
|
243
|
+
const worktreeRoot = resolveWorktreeRoot(cwd);
|
|
244
|
+
if (worktreeRoot !== cwd) cwd = worktreeRoot;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// --ws workstream
|
|
248
|
+
{
|
|
249
|
+
const wsEqArg = argv.find((a) => a.startsWith("--ws="));
|
|
250
|
+
const wsIdx = argv.indexOf("--ws");
|
|
251
|
+
let ws: string | null = null;
|
|
252
|
+
if (wsEqArg) {
|
|
253
|
+
ws = wsEqArg.slice("--ws=".length).trim();
|
|
254
|
+
if (!ws) gsdError("Missing value for --ws");
|
|
255
|
+
argv.splice(argv.indexOf(wsEqArg), 1);
|
|
256
|
+
} else if (wsIdx !== -1) {
|
|
257
|
+
ws = argv[wsIdx + 1];
|
|
258
|
+
if (!ws || ws.startsWith("--")) gsdError("Missing value for --ws");
|
|
259
|
+
argv.splice(wsIdx, 2);
|
|
260
|
+
} else if (process.env["GSD_WORKSTREAM"]) {
|
|
261
|
+
ws = process.env["GSD_WORKSTREAM"].trim();
|
|
262
|
+
} else {
|
|
263
|
+
ws = getActiveWorkstream(cwd);
|
|
264
|
+
}
|
|
265
|
+
if (ws && !/^[a-zA-Z0-9_-]+$/.test(ws)) gsdError("Invalid workstream name");
|
|
266
|
+
if (ws) process.env["GSD_WORKSTREAM"] = ws;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --raw
|
|
270
|
+
const rawIndex = argv.indexOf("--raw");
|
|
271
|
+
const raw = rawIndex !== -1;
|
|
272
|
+
if (rawIndex !== -1) argv.splice(rawIndex, 1);
|
|
273
|
+
|
|
274
|
+
// --output
|
|
275
|
+
let outputFormat: OutputFormat = "json";
|
|
276
|
+
{
|
|
277
|
+
const outputIdx = argv.findIndex((a) => a === "--output" || a === "-o");
|
|
278
|
+
if (outputIdx !== -1) {
|
|
279
|
+
const fmt = argv[outputIdx + 1];
|
|
280
|
+
if (!fmt || fmt.startsWith("--")) gsdError("Missing value for --output");
|
|
281
|
+
if (fmt !== "json" && fmt !== "toon") gsdError('--output must be "json" or "toon"');
|
|
282
|
+
outputFormat = fmt as OutputFormat;
|
|
283
|
+
argv.splice(outputIdx, 2);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// --pick
|
|
288
|
+
let pickPath: string | null = null;
|
|
289
|
+
let legacyPickField: string | null = null;
|
|
290
|
+
{
|
|
291
|
+
const pickIdx = argv.findIndex((a) => a === "--pick" || a === "-p");
|
|
292
|
+
if (pickIdx !== -1) {
|
|
293
|
+
pickPath = argv[pickIdx + 1];
|
|
294
|
+
if (!pickPath || pickPath.startsWith("--")) gsdError("Missing value for --pick");
|
|
295
|
+
argv.splice(pickIdx, 2);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!argv[0]) {
|
|
300
|
+
printHelp();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Inject cwd back as --cwd for oclif commands (BaseCommand reads it from flags)
|
|
305
|
+
const oclifArgv = [...argv, "--cwd", cwd];
|
|
306
|
+
if (raw) oclifArgv.push("--raw");
|
|
307
|
+
|
|
308
|
+
// ── Try oclif command map first ────────────────────────────────────────────
|
|
309
|
+
const commandMap = await buildCommandMap();
|
|
310
|
+
const key = findCommandKey(argv, commandMap);
|
|
311
|
+
|
|
312
|
+
if (key) {
|
|
313
|
+
// Build argv for the oclif command: strip the command key tokens, pass rest
|
|
314
|
+
const keyTokenCount = key.split(" ").length;
|
|
315
|
+
const cmdArgv = [...argv.slice(keyTokenCount), "--cwd", cwd];
|
|
316
|
+
if (raw) cmdArgv.push("--raw");
|
|
317
|
+
|
|
318
|
+
// Output interception for --pick / --output toon
|
|
319
|
+
if (legacyPickField || pickPath || outputFormat !== "json") {
|
|
320
|
+
const origWriteSync = fs.writeSync.bind(fs);
|
|
321
|
+
const chunks: string[] = [];
|
|
322
|
+
(fs as unknown as { writeSync: typeof fs.writeSync }).writeSync = (
|
|
323
|
+
fd: number,
|
|
324
|
+
data: string | Buffer | NodeJS.ArrayBufferView,
|
|
325
|
+
...rest: unknown[]
|
|
326
|
+
): number => {
|
|
327
|
+
if (fd === 1) { chunks.push(String(data)); return String(data).length; }
|
|
328
|
+
return (origWriteSync as (...args: unknown[]) => number)(fd, data, ...rest);
|
|
329
|
+
};
|
|
330
|
+
try {
|
|
331
|
+
await commandMap[key].run(cmdArgv);
|
|
332
|
+
} finally {
|
|
333
|
+
(fs as unknown as { writeSync: typeof fs.writeSync }).writeSync =
|
|
334
|
+
origWriteSync as typeof fs.writeSync;
|
|
335
|
+
const captured = chunks.join("");
|
|
336
|
+
try {
|
|
337
|
+
const obj = JSON.parse(captured);
|
|
338
|
+
let result: unknown = obj;
|
|
339
|
+
if (legacyPickField) result = extractField(obj, legacyPickField) ?? "";
|
|
340
|
+
origWriteSync(1, formatOutput(result, outputFormat, pickPath ?? undefined));
|
|
341
|
+
} catch { origWriteSync(1, captured); }
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
await commandMap[key].run(cmdArgv);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ── Fall through to legacy switch for remaining commands ───────────────────
|
|
351
|
+
const command = argv[0];
|
|
352
|
+
const args = argv;
|
|
353
|
+
|
|
354
|
+
// Root resolution
|
|
355
|
+
const SKIP_ROOT_RESOLUTION = new Set([
|
|
356
|
+
"generate-slug", "current-timestamp", "verify-path-exists",
|
|
357
|
+
"verify-summary", "template", "frontmatter", "generate-model-profiles-md",
|
|
358
|
+
]);
|
|
359
|
+
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
|
360
|
+
cwd = findProjectRoot(cwd);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const runLegacy = async () => {
|
|
364
|
+
await runLegacyCommand(command, args, cwd, raw);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
if (legacyPickField || pickPath || outputFormat !== "json") {
|
|
368
|
+
const origWriteSync = fs.writeSync.bind(fs);
|
|
369
|
+
const chunks: string[] = [];
|
|
370
|
+
(fs as unknown as { writeSync: typeof fs.writeSync }).writeSync = (
|
|
371
|
+
fd: number,
|
|
372
|
+
data: string | Buffer | NodeJS.ArrayBufferView,
|
|
373
|
+
...rest: unknown[]
|
|
374
|
+
): number => {
|
|
375
|
+
if (fd === 1) { chunks.push(String(data)); return String(data).length; }
|
|
376
|
+
return (origWriteSync as (...args: unknown[]) => number)(fd, data, ...rest);
|
|
377
|
+
};
|
|
378
|
+
try {
|
|
379
|
+
await runLegacy();
|
|
380
|
+
} finally {
|
|
381
|
+
(fs as unknown as { writeSync: typeof fs.writeSync }).writeSync =
|
|
382
|
+
origWriteSync as typeof fs.writeSync;
|
|
383
|
+
const captured = chunks.join("");
|
|
384
|
+
try {
|
|
385
|
+
const obj = JSON.parse(captured);
|
|
386
|
+
let result: unknown = obj;
|
|
387
|
+
if (legacyPickField) result = extractField(obj, legacyPickField) ?? "";
|
|
388
|
+
origWriteSync(1, formatOutput(result, outputFormat, pickPath ?? undefined));
|
|
389
|
+
} catch { origWriteSync(1, captured); }
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
await runLegacy();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function printHelp(): void {
|
|
398
|
+
process.stdout.write([
|
|
399
|
+
"Usage: pi-gsd-tools <command> [subcommand] [args] [--raw] [--cwd <path>] [--ws <name>]",
|
|
400
|
+
"",
|
|
401
|
+
"Commands: state, init, roadmap, config-get, config-set, phase, milestone,",
|
|
402
|
+
" validate, verify, workstream, scaffold, commit, frontmatter, template,",
|
|
403
|
+
" progress, stats, todo, summary-extract, wxp, resolve-model, find-phase,",
|
|
404
|
+
" generate-slug, current-timestamp, list-todos, verify-path-exists,",
|
|
405
|
+
" audit-uat, uat, generate-model-profiles-md, and more.",
|
|
406
|
+
"",
|
|
407
|
+
"Add --help to any command for details.",
|
|
408
|
+
].join("\n") + "\n");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ─── Legacy command router (for commands not yet with oclif classes) ──────────
|
|
412
|
+
|
|
413
|
+
async function runLegacyCommand(
|
|
414
|
+
command: string,
|
|
415
|
+
args: string[],
|
|
416
|
+
cwd: string,
|
|
417
|
+
raw: boolean,
|
|
418
|
+
): Promise<void> {
|
|
419
|
+
switch (command) {
|
|
420
|
+
case "resolve-model": {
|
|
421
|
+
const { cmdResolveModel } = await import("./lib/commands.js");
|
|
422
|
+
cmdResolveModel(cwd, args[1], raw);
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case "find-phase": {
|
|
426
|
+
const { cmdFindPhase } = await import("./lib/phase.js");
|
|
427
|
+
cmdFindPhase(cwd, args[1], raw);
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case "commit-to-subrepo": {
|
|
431
|
+
const { cmdCommitToSubrepo } = await import("./lib/commands.js");
|
|
432
|
+
const filesIndex = args.indexOf("--files");
|
|
433
|
+
const messageArgs = args.slice(1, filesIndex !== -1 ? filesIndex : args.length)
|
|
434
|
+
.filter((a) => !a.startsWith("--"));
|
|
435
|
+
const files = filesIndex !== -1
|
|
436
|
+
? args.slice(filesIndex + 1).filter((a) => !a.startsWith("--"))
|
|
437
|
+
: [];
|
|
438
|
+
cmdCommitToSubrepo(cwd, messageArgs.join(" ") || undefined, files, raw);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case "verify-summary": {
|
|
442
|
+
const { cmdVerifySummary } = await import("./lib/verify.js");
|
|
443
|
+
const checkCount = args[2] ? parseInt(args[2], 10) : 2;
|
|
444
|
+
cmdVerifySummary(cwd, args[1], checkCount, raw);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
case "generate-slug": {
|
|
448
|
+
const { cmdGenerateSlug } = await import("./lib/commands.js");
|
|
449
|
+
cmdGenerateSlug(args[1], raw);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case "current-timestamp": {
|
|
453
|
+
const { cmdCurrentTimestamp } = await import("./lib/commands.js");
|
|
454
|
+
cmdCurrentTimestamp(args[1], raw);
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case "list-todos": {
|
|
458
|
+
const { cmdListTodos } = await import("./lib/commands.js");
|
|
459
|
+
cmdListTodos(cwd, args[1], raw);
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
case "verify-path-exists": {
|
|
463
|
+
const { cmdVerifyPathExists } = await import("./lib/commands.js");
|
|
464
|
+
cmdVerifyPathExists(cwd, args[1], raw);
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
case "phases": {
|
|
468
|
+
const { cmdPhasesList } = await import("./lib/phase.js");
|
|
469
|
+
cmdPhasesList(cwd, {}, raw);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
case "requirements": {
|
|
473
|
+
const { cmdRequirementsMarkComplete } = await import("./lib/milestone.js");
|
|
474
|
+
if (args[1] === "mark-complete") cmdRequirementsMarkComplete(cwd, args.slice(2), raw);
|
|
475
|
+
else gsdError("Unknown requirements subcommand. Available: mark-complete");
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
case "uat": {
|
|
479
|
+
const uat = await import("./lib/uat.js");
|
|
480
|
+
if (args[1] === "render-checkpoint") {
|
|
481
|
+
uat.cmdRenderCheckpoint(
|
|
482
|
+
cwd,
|
|
483
|
+
parseNamedArgs(args, ["file"]) as { file?: string | null },
|
|
484
|
+
raw,
|
|
485
|
+
);
|
|
486
|
+
} else gsdError("Unknown uat subcommand. Available: render-checkpoint");
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case "state-snapshot": {
|
|
490
|
+
const { cmdStateSnapshot } = await import("./lib/state.js");
|
|
491
|
+
cmdStateSnapshot(cwd, raw);
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
case "agent-skills": {
|
|
495
|
+
const { cmdAgentSkills } = await import("./lib/init.js");
|
|
496
|
+
cmdAgentSkills(cwd, args[1], raw);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case "history-digest": {
|
|
500
|
+
const { cmdHistoryDigest } = await import("./lib/commands.js");
|
|
501
|
+
cmdHistoryDigest(cwd, raw);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
case "scan-sessions": {
|
|
505
|
+
const { cmdScanSessions } = await import("./lib/profile-pipeline.js");
|
|
506
|
+
const pathIdx = args.indexOf("--path"), harnessIdx = args.indexOf("--harness");
|
|
507
|
+
await cmdScanSessions(
|
|
508
|
+
pathIdx !== -1 ? args[pathIdx + 1] : null,
|
|
509
|
+
{ verbose: args.includes("--verbose"), json: args.includes("--json"),
|
|
510
|
+
harness: harnessIdx !== -1 ? args[harnessIdx + 1] : null },
|
|
511
|
+
raw,
|
|
512
|
+
);
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case "extract-messages": {
|
|
516
|
+
const { cmdExtractMessages } = await import("./lib/profile-pipeline.js");
|
|
517
|
+
const sessionIdx = args.indexOf("--session");
|
|
518
|
+
const limitIdx = args.indexOf("--limit");
|
|
519
|
+
const pathIdx = args.indexOf("--path");
|
|
520
|
+
const isPiDirArg = (s: string) => s.startsWith("--") && s.endsWith("--") && s.length > 4;
|
|
521
|
+
if (!args[1] || (args[1].startsWith("--") && !isPiDirArg(args[1])))
|
|
522
|
+
gsdError("Usage: pi-gsd-tools extract-messages <project> [--session <id>] [--limit N]");
|
|
523
|
+
await cmdExtractMessages(
|
|
524
|
+
args[1],
|
|
525
|
+
{ sessionId: sessionIdx !== -1 ? args[sessionIdx + 1] : null,
|
|
526
|
+
limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : null },
|
|
527
|
+
raw,
|
|
528
|
+
pathIdx !== -1 ? args[pathIdx + 1] : null,
|
|
529
|
+
);
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
case "profile-sample": {
|
|
533
|
+
const { cmdProfileSample } = await import("./lib/profile-pipeline.js");
|
|
534
|
+
const pathIdx = args.indexOf("--path"), limitIdx = args.indexOf("--limit");
|
|
535
|
+
const maxPerIdx = args.indexOf("--max-per-project"), maxCharsIdx = args.indexOf("--max-chars");
|
|
536
|
+
const harnessIdx = args.indexOf("--harness");
|
|
537
|
+
await cmdProfileSample(
|
|
538
|
+
pathIdx !== -1 ? args[pathIdx + 1] : null,
|
|
539
|
+
{ limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 150,
|
|
540
|
+
maxPerProject: maxPerIdx !== -1 ? parseInt(args[maxPerIdx + 1], 10) : null,
|
|
541
|
+
harness: harnessIdx !== -1 ? args[harnessIdx + 1] : null,
|
|
542
|
+
maxChars: maxCharsIdx !== -1 ? parseInt(args[maxCharsIdx + 1], 10) : 500 },
|
|
543
|
+
raw,
|
|
544
|
+
);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case "write-profile": {
|
|
548
|
+
const { cmdWriteProfile } = await import("./lib/profile-output.js");
|
|
549
|
+
const inputIdx = args.indexOf("--input"), outputIdx = args.indexOf("--output");
|
|
550
|
+
if (inputIdx === -1) gsdError("--input <analysis-json-path> is required");
|
|
551
|
+
cmdWriteProfile(cwd, { input: args[inputIdx + 1], output: outputIdx !== -1 ? args[outputIdx + 1] : null }, raw);
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case "profile-questionnaire": {
|
|
555
|
+
const { cmdProfileQuestionnaire } = await import("./lib/profile-output.js");
|
|
556
|
+
const answersIdx = args.indexOf("--answers");
|
|
557
|
+
cmdProfileQuestionnaire({ answers: answersIdx !== -1 ? args[answersIdx + 1] : null }, raw);
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
case "generate-dev-preferences": {
|
|
561
|
+
const { cmdGenerateDevPreferences } = await import("./lib/profile-output.js");
|
|
562
|
+
const analysisIdx = args.indexOf("--analysis"), outputIdx = args.indexOf("--output");
|
|
563
|
+
const stackIdx = args.indexOf("--stack");
|
|
564
|
+
cmdGenerateDevPreferences(cwd, {
|
|
565
|
+
analysis: analysisIdx !== -1 ? args[analysisIdx + 1] : null,
|
|
566
|
+
output: outputIdx !== -1 ? args[outputIdx + 1] : null,
|
|
567
|
+
stack: stackIdx !== -1 ? args[stackIdx + 1] : null,
|
|
568
|
+
}, raw);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
case "generate-claude-profile": {
|
|
572
|
+
const { cmdGenerateClaudeProfile } = await import("./lib/profile-output.js");
|
|
573
|
+
const analysisIdx = args.indexOf("--analysis"), outputIdx = args.indexOf("--output");
|
|
574
|
+
cmdGenerateClaudeProfile(cwd, {
|
|
575
|
+
analysis: analysisIdx !== -1 ? args[analysisIdx + 1] : null,
|
|
576
|
+
output: outputIdx !== -1 ? args[outputIdx + 1] : null,
|
|
577
|
+
global: args.includes("--global"),
|
|
578
|
+
}, raw);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
case "generate-claude-md": {
|
|
582
|
+
const { cmdGenerateClaudeMd } = await import("./lib/profile-output.js");
|
|
583
|
+
const outputIdx = args.indexOf("--output"), harnessIdx = args.indexOf("--harness");
|
|
584
|
+
cmdGenerateClaudeMd(cwd, {
|
|
585
|
+
output: outputIdx !== -1 ? args[outputIdx + 1] : null,
|
|
586
|
+
auto: args.includes("--auto"),
|
|
587
|
+
force: args.includes("--force"),
|
|
588
|
+
harness: harnessIdx !== -1 ? args[harnessIdx + 1] : null,
|
|
589
|
+
}, raw);
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
case "generate-model-profiles-md": {
|
|
593
|
+
const { generateModelProfilesMd } = await import("./lib/model-profiles.js");
|
|
594
|
+
const outputIdx = args.indexOf("--output");
|
|
595
|
+
const content = generateModelProfilesMd();
|
|
596
|
+
if (args.includes("--stdout")) { process.stdout.write(content); break; }
|
|
597
|
+
const outPath = outputIdx !== -1
|
|
598
|
+
? path.resolve(args[outputIdx + 1])
|
|
599
|
+
: path.resolve(__dirname, "..", "references", "model-profiles.md");
|
|
600
|
+
fs.writeFileSync(outPath, content, "utf-8");
|
|
601
|
+
raw ? process.stdout.write(outPath) : process.stdout.write(`Wrote ${outPath}\n`);
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
case "websearch": {
|
|
605
|
+
const { cmdWebsearch } = await import("./lib/commands.js");
|
|
606
|
+
const limitIdx = args.indexOf("--limit"), freshnessIdx = args.indexOf("--freshness");
|
|
607
|
+
await cmdWebsearch(args[1], {
|
|
608
|
+
limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 10,
|
|
609
|
+
freshness: freshnessIdx !== -1 ? args[freshnessIdx + 1] : null,
|
|
610
|
+
}, raw);
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
case "map-codebase": {
|
|
614
|
+
const { cmdInitMapCodebase } = await import("./lib/init.js");
|
|
615
|
+
await cmdInitMapCodebase(cwd, raw);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
case "new-project":
|
|
619
|
+
case "new-milestone":
|
|
620
|
+
case "plan-phase":
|
|
621
|
+
case "execute-phase":
|
|
622
|
+
case "verify-work":
|
|
623
|
+
case "phase-op":
|
|
624
|
+
case "milestone-op":
|
|
625
|
+
case "resume":
|
|
626
|
+
case "quick":
|
|
627
|
+
case "manager":
|
|
628
|
+
case "progress":
|
|
629
|
+
case "new-workspace":
|
|
630
|
+
case "list-workspaces":
|
|
631
|
+
case "remove-workspace": {
|
|
632
|
+
// These are routed via 'init <workflow>' in the oclif map
|
|
633
|
+
gsdError(`Use: pi-gsd-tools init ${command} [args]`);
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
default:
|
|
637
|
+
gsdError(`Unknown command: ${command}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
main().catch((err) => {
|
|
642
|
+
process.stderr.write("Fatal: " + (err as Error).message + "\n");
|
|
643
|
+
process.exit(1);
|
|
644
|
+
});
|