speckit-assistant 0.1.3 → 0.1.5
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +56 -20
- package/.next/app-path-routes-manifest.json +6 -1
- package/.next/build-manifest.json +5 -5
- package/.next/cache/next-devtools-config.json +1 -0
- package/.next/cache/webpack/client-development/0.pack.gz +0 -0
- package/.next/cache/webpack/client-development/index.pack.gz +0 -0
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/11.pack +0 -0
- package/.next/cache/webpack/client-production/12.pack +0 -0
- package/.next/cache/webpack/client-production/13.pack +0 -0
- package/.next/cache/webpack/client-production/15.pack +0 -0
- package/.next/cache/webpack/client-production/16.pack +0 -0
- package/.next/cache/webpack/client-production/17.pack +0 -0
- package/.next/cache/webpack/client-production/18.pack +0 -0
- package/.next/cache/webpack/client-production/19.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/20.pack +0 -0
- package/.next/cache/webpack/client-production/21.pack +0 -0
- package/.next/cache/webpack/client-production/22.pack +0 -0
- package/.next/cache/webpack/client-production/23.pack +0 -0
- package/.next/cache/webpack/client-production/24.pack +0 -0
- package/.next/cache/webpack/client-production/25.pack +0 -0
- package/.next/cache/webpack/client-production/26.pack +0 -0
- package/.next/cache/webpack/client-production/27.pack +0 -0
- package/.next/cache/webpack/client-production/28.pack +0 -0
- package/.next/cache/webpack/client-production/29.pack +0 -0
- package/.next/cache/webpack/client-production/3.pack +0 -0
- package/.next/cache/webpack/client-production/30.pack +0 -0
- package/.next/cache/webpack/client-production/31.pack +0 -0
- package/.next/cache/webpack/client-production/32.pack +0 -0
- package/.next/cache/webpack/client-production/33.pack +0 -0
- package/.next/cache/webpack/client-production/34.pack +0 -0
- package/.next/cache/webpack/client-production/4.pack +0 -0
- package/.next/cache/webpack/client-production/6.pack +0 -0
- package/.next/cache/webpack/client-production/7.pack +0 -0
- package/.next/cache/webpack/client-production/8.pack +0 -0
- package/.next/cache/webpack/client-production/9.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/1.pack +0 -0
- package/.next/cache/webpack/server-production/10.pack +0 -0
- package/.next/cache/webpack/server-production/11.pack +0 -0
- package/.next/cache/webpack/server-production/12.pack +0 -0
- package/.next/cache/webpack/server-production/13.pack +0 -0
- package/.next/cache/webpack/server-production/14.pack +0 -0
- package/.next/cache/webpack/server-production/16.pack +0 -0
- package/.next/cache/webpack/server-production/18.pack +0 -0
- package/.next/cache/webpack/server-production/19.pack +0 -0
- package/.next/cache/webpack/server-production/2.pack +0 -0
- package/.next/cache/webpack/server-production/20.pack +0 -0
- package/.next/cache/webpack/server-production/21.pack +0 -0
- package/.next/cache/webpack/server-production/23.pack +0 -0
- package/.next/cache/webpack/server-production/24.pack +0 -0
- package/.next/cache/webpack/server-production/25.pack +0 -0
- package/.next/cache/webpack/server-production/26.pack +0 -0
- package/.next/cache/webpack/server-production/27.pack +0 -0
- package/.next/cache/webpack/server-production/28.pack +0 -0
- package/.next/cache/webpack/server-production/29.pack +0 -0
- package/.next/cache/webpack/server-production/3.pack +0 -0
- package/.next/cache/webpack/server-production/30.pack +0 -0
- package/.next/cache/webpack/server-production/31.pack +0 -0
- package/.next/cache/webpack/server-production/32.pack +0 -0
- package/.next/cache/webpack/server-production/33.pack +0 -0
- package/.next/cache/webpack/server-production/4.pack +0 -0
- package/.next/cache/webpack/server-production/5.pack +0 -0
- package/.next/cache/webpack/server-production/6.pack +0 -0
- package/.next/cache/webpack/server-production/7.pack +0 -0
- package/.next/cache/webpack/server-production/9.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/prerender-manifest.json +7 -7
- package/.next/react-loadable-manifest.json +8 -1
- package/.next/required-server-files.json +3 -0
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/api/feature/route.js +1 -1
- package/.next/server/app/api/feature/route.js.nft.json +1 -1
- package/.next/server/app/api/feature/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/file/route.js +1 -1
- package/.next/server/app/api/file/route.js.nft.json +1 -1
- package/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/git/route.js +1 -0
- package/.next/server/app/api/git/route.js.nft.json +1 -0
- package/.next/server/app/api/git/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/personas/route.js +1 -0
- package/.next/server/app/api/personas/route.js.nft.json +1 -0
- package/.next/server/app/api/personas/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/phase/route.js +3 -3
- package/.next/server/app/api/phase/route.js.nft.json +1 -1
- package/.next/server/app/api/phase/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/state/route.js +1 -1
- package/.next/server/app/api/state/route.js.nft.json +1 -1
- package/.next/server/app/api/state/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/state/watch/route.js +2 -2
- package/.next/server/app/api/state/watch/route.js.nft.json +1 -1
- package/.next/server/app/api/state/watch/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/task/route.js +1 -1
- package/.next/server/app/api/task/route.js.nft.json +1 -1
- package/.next/server/app/api/task/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/terminal/input/route.js +1 -0
- package/.next/server/app/api/terminal/input/route.js.nft.json +1 -0
- package/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/terminal/resize/route.js +1 -0
- package/.next/server/app/api/terminal/resize/route.js.nft.json +1 -0
- package/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/terminal/stream/route.js +3 -0
- package/.next/server/app/api/terminal/stream/route.js.nft.json +1 -0
- package/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -0
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/page.js +26 -7
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +6 -1
- package/.next/server/chunks/607.js +2 -2
- package/.next/server/chunks/897.js +13 -7
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/8CUCTC2XnkR-leKsH4TwE/_buildManifest.js +1 -0
- package/.next/static/chunks/111acf76-29d5e5905666f1e0.js +18 -0
- package/.next/static/chunks/343.91af0d46f6df0f05.js +1 -0
- package/.next/static/chunks/873-a995367ae371a5e4.js +1 -0
- package/.next/static/chunks/app/api/feature/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/file/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/git/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/personas/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/phase/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/state/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/state/watch/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/task/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/terminal/input/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/terminal/resize/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/api/terminal/stream/route-b4fbc89d13fef983.js +1 -0
- package/.next/static/chunks/app/page-b3dea12c644f2054.js +1 -0
- package/.next/static/chunks/webpack-c997c7bd3e8f7cdf.js +1 -0
- package/.next/static/css/0024e90648d29466.css +3 -0
- package/.next/static/css/{008a05b0ad6b854a.css → af3f40f14f702ff7.css} +1 -1
- package/.next/trace +3 -3
- package/.next/types/app/api/git/route.ts +347 -0
- package/.next/types/app/api/personas/route.ts +347 -0
- package/.next/types/app/api/terminal/input/route.ts +347 -0
- package/.next/types/app/api/terminal/resize/route.ts +347 -0
- package/.next/types/app/api/terminal/stream/route.ts +347 -0
- package/.next/types/routes.d.ts +6 -1
- package/.next/types/validator.ts +45 -0
- package/bin/adapters/primary/api/terminalManager.js +92 -0
- package/bin/adapters/secondary/agent/ProcessAgentRunner.js +170 -65
- package/bin/adapters/secondary/fs/FSWorkspaceRepository.js +40 -5
- package/bin/adapters/secondary/pty/ptyLoader.js +128 -0
- package/bin/app/api/git/route.js +156 -0
- package/bin/app/api/personas/route.js +76 -0
- package/bin/app/api/phase/route.js +18 -5
- package/bin/app/api/state/watch/route.js +7 -2
- package/bin/app/api/terminal/input/route.js +18 -0
- package/bin/app/api/terminal/resize/route.js +18 -0
- package/bin/app/api/terminal/stream/route.js +44 -0
- package/bin/domain/models/personas.js +65 -0
- package/bin/domain/services/WorkflowService.js +79 -5
- package/next.config.mjs +3 -0
- package/package.json +13 -1
- package/.next/static/Bxo1ckOxvQcndtSKD9Yna/_buildManifest.js +0 -1
- package/.next/static/chunks/590-a6568595ecd2a994.js +0 -1
- package/.next/static/chunks/app/api/feature/route-bb3c1a82e892ab58.js +0 -1
- package/.next/static/chunks/app/api/file/route-bb3c1a82e892ab58.js +0 -1
- package/.next/static/chunks/app/api/phase/route-bb3c1a82e892ab58.js +0 -1
- package/.next/static/chunks/app/api/state/route-bb3c1a82e892ab58.js +0 -1
- package/.next/static/chunks/app/api/state/watch/route-bb3c1a82e892ab58.js +0 -1
- package/.next/static/chunks/app/api/task/route-bb3c1a82e892ab58.js +0 -1
- package/.next/static/chunks/app/page-8a5248f7704cde29.js +0 -1
- package/.next/static/chunks/webpack-c460f8e58a9e9eff.js +0 -1
- package/.next/static/css/6fd2e11db3a59771.css +0 -3
- /package/.next/static/{Bxo1ckOxvQcndtSKD9Yna → 8CUCTC2XnkR-leKsH4TwE}/_ssgManifest.js +0 -0
|
@@ -34,128 +34,233 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ProcessAgentRunner = void 0;
|
|
37
|
-
const
|
|
37
|
+
const ptyLoader_1 = require("../pty/ptyLoader");
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const PHASE_COMMANDS = {
|
|
41
|
-
constitution:
|
|
42
|
-
specification:
|
|
43
|
-
clarification:
|
|
44
|
-
planning:
|
|
45
|
-
checklist:
|
|
46
|
-
analyze:
|
|
47
|
-
tasks:
|
|
48
|
-
taskstoissues:
|
|
49
|
-
implementation:
|
|
41
|
+
constitution: "/speckit.constitution",
|
|
42
|
+
specification: "/speckit.specify",
|
|
43
|
+
clarification: "/speckit.clarify",
|
|
44
|
+
planning: "/speckit.plan",
|
|
45
|
+
checklist: "/speckit.checklist",
|
|
46
|
+
analyze: "/speckit.analyze",
|
|
47
|
+
tasks: "/speckit.tasks",
|
|
48
|
+
taskstoissues: "/speckit.taskstoissues",
|
|
49
|
+
implementation: "/speckit.implement",
|
|
50
50
|
};
|
|
51
51
|
class ProcessAgentRunner {
|
|
52
|
+
activeProcesses = new Map();
|
|
53
|
+
phaseKey(featureName, phase) {
|
|
54
|
+
return `${featureName || "global"}-${phase}`;
|
|
55
|
+
}
|
|
56
|
+
personaKey(featureName, id) {
|
|
57
|
+
return `${featureName}-impl-persona-${id}`;
|
|
58
|
+
}
|
|
59
|
+
async writeStdin(phase, featureName, text, personaId) {
|
|
60
|
+
const procKey = personaId && featureName
|
|
61
|
+
? this.personaKey(featureName, personaId)
|
|
62
|
+
: this.phaseKey(featureName, phase);
|
|
63
|
+
const child = this.activeProcesses.get(procKey);
|
|
64
|
+
if (child) {
|
|
65
|
+
// The frontend xterm sends raw keystrokes (arrow keys, Enter as '\r', etc.)
|
|
66
|
+
// so we forward them verbatim — this is what lets interactive pickers like
|
|
67
|
+
// the clarify Q&A be navigated, not just line-based answers.
|
|
68
|
+
child.write(text);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
async resize(phase, featureName, cols, rows, personaId) {
|
|
74
|
+
const procKey = personaId && featureName
|
|
75
|
+
? this.personaKey(featureName, personaId)
|
|
76
|
+
: this.phaseKey(featureName, phase);
|
|
77
|
+
const child = this.activeProcesses.get(procKey);
|
|
78
|
+
if (!child)
|
|
79
|
+
return false;
|
|
80
|
+
try {
|
|
81
|
+
child.resize(cols, rows);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async stop(phase, featureName, personaId) {
|
|
89
|
+
const procKey = personaId && featureName
|
|
90
|
+
? this.personaKey(featureName, personaId)
|
|
91
|
+
: this.phaseKey(featureName, phase);
|
|
92
|
+
const child = this.activeProcesses.get(procKey);
|
|
93
|
+
if (child) {
|
|
94
|
+
try {
|
|
95
|
+
child.kill();
|
|
96
|
+
this.activeProcesses.delete(procKey);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
52
105
|
async runPhase(workspacePath, phase, featureName, agentConfig, userPrompt, onData) {
|
|
53
|
-
const specArg = phase !==
|
|
54
|
-
const slashCmd = `${PHASE_COMMANDS[phase]}${specArg ? ` ${specArg}` :
|
|
106
|
+
const specArg = phase !== "constitution" && featureName ? `specs/${featureName}` : null;
|
|
107
|
+
const slashCmd = `${PHASE_COMMANDS[phase]}${specArg ? ` ${specArg}` : ""}`;
|
|
55
108
|
const context = userPrompt?.trim();
|
|
56
109
|
const fullPrompt = context ? `${context}\n\n${slashCmd}` : slashCmd;
|
|
110
|
+
return this.spawnAgent({
|
|
111
|
+
workspacePath,
|
|
112
|
+
procKey: this.phaseKey(featureName, phase),
|
|
113
|
+
doneTag: phase,
|
|
114
|
+
agentConfig,
|
|
115
|
+
fullPrompt,
|
|
116
|
+
onData,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async runPersona(workspacePath, featureName, persona, agentConfig, onData) {
|
|
120
|
+
// Personas always operate on a specific feature's artifacts.
|
|
121
|
+
const fullPrompt = `${persona.command} specs/${featureName}`;
|
|
122
|
+
return this.spawnAgent({
|
|
123
|
+
workspacePath,
|
|
124
|
+
procKey: this.personaKey(featureName, persona.id),
|
|
125
|
+
doneTag: `persona:${persona.id}`,
|
|
126
|
+
agentConfig,
|
|
127
|
+
fullPrompt,
|
|
128
|
+
onData,
|
|
129
|
+
persona,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// Shared spawn path for both phase and persona runs: spawns the agent CLI
|
|
133
|
+
// under a PTY (so interactive CLIs detect a TTY), streams output, and resolves
|
|
134
|
+
// with the exit code.
|
|
135
|
+
spawnAgent(opts) {
|
|
136
|
+
const { workspacePath, procKey, doneTag, agentConfig, fullPrompt, onData } = opts;
|
|
57
137
|
const { cmd, args, stdin } = this.buildSpawnArgs(agentConfig, fullPrompt);
|
|
58
|
-
const isWin = process.platform === 'win32';
|
|
59
138
|
return new Promise((resolve) => {
|
|
60
|
-
onData?.(`Running: ${cmd} ${args.join(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
child
|
|
80
|
-
|
|
81
|
-
|
|
139
|
+
onData?.(`Running: ${cmd} ${args.join(" ")}\n\n`);
|
|
140
|
+
// Spawn the agent under a real pseudo-terminal. This is what makes
|
|
141
|
+
// interactive CLIs (e.g. the clarify Q&A) detect a TTY via isatty() and
|
|
142
|
+
// actually prompt the user instead of running single-shot. We still go
|
|
143
|
+
// through a login shell so PATH/aliases resolve the agent binary the same
|
|
144
|
+
// way they would in the user's own terminal.
|
|
145
|
+
const isWin = process.platform === "win32";
|
|
146
|
+
const ptyFile = isWin
|
|
147
|
+
? process.env.COMSPEC || "cmd.exe"
|
|
148
|
+
: process.env.SHELL || "/bin/bash";
|
|
149
|
+
const ptyArgs = isWin
|
|
150
|
+
? ["/c", [cmd, ...args].join(" ")]
|
|
151
|
+
: [
|
|
152
|
+
"-l",
|
|
153
|
+
"-c",
|
|
154
|
+
[cmd, ...args]
|
|
155
|
+
.map((a) => `'${a.replace(/'/g, "'\\''")}'`)
|
|
156
|
+
.join(" "),
|
|
157
|
+
];
|
|
158
|
+
const child = (0, ptyLoader_1.loadPty)().spawn(ptyFile, ptyArgs, {
|
|
159
|
+
name: "xterm-256color",
|
|
160
|
+
cols: 120,
|
|
161
|
+
rows: 30,
|
|
162
|
+
cwd: workspacePath,
|
|
163
|
+
env: {
|
|
164
|
+
...process.env,
|
|
165
|
+
TERM: "xterm-256color",
|
|
166
|
+
...(opts.persona
|
|
167
|
+
? {
|
|
168
|
+
SPECKIT_PERSONA_ID: opts.persona.id || "",
|
|
169
|
+
SPECKIT_PERSONA_LABEL: opts.persona.label || "",
|
|
170
|
+
SPECKIT_PERSONA_MODEL: opts.persona.model || "",
|
|
171
|
+
SPECKIT_PERSONA_SYSTEM_PROMPT: opts.persona.systemPrompt || "",
|
|
172
|
+
SPECKIT_PERSONA_CAPABILITIES: Array.isArray(opts.persona.capabilities)
|
|
173
|
+
? opts.persona.capabilities.join(",")
|
|
174
|
+
: "",
|
|
175
|
+
SPECKIT_PERSONA_TOOLS: Array.isArray(opts.persona.tools)
|
|
176
|
+
? opts.persona.tools.join(",")
|
|
177
|
+
: "",
|
|
178
|
+
}
|
|
179
|
+
: {}),
|
|
180
|
+
},
|
|
82
181
|
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
182
|
+
this.activeProcesses.set(procKey, child);
|
|
183
|
+
// For agents that receive their prompt over stdin, feed it now and send
|
|
184
|
+
// EOF (Ctrl-D) so the single-shot CLI starts processing. Agents that take
|
|
185
|
+
// the prompt as an argument keep the channel open so the user can answer
|
|
186
|
+
// interactive prompts (e.g. clarify) through writeStdin().
|
|
187
|
+
if (stdin !== undefined) {
|
|
188
|
+
child.write(stdin);
|
|
189
|
+
child.write("\x04");
|
|
190
|
+
}
|
|
191
|
+
child.onData((data) => {
|
|
192
|
+
onData?.(data);
|
|
86
193
|
});
|
|
87
|
-
child.
|
|
88
|
-
|
|
194
|
+
child.onExit(({ exitCode }) => {
|
|
195
|
+
this.activeProcesses.delete(procKey);
|
|
89
196
|
onData?.(`\nProcess exited with code ${exitCode}\n`);
|
|
90
197
|
// Write the phase done file so local watch logs it
|
|
91
198
|
try {
|
|
92
|
-
const runtimeDir = path.join(workspacePath,
|
|
199
|
+
const runtimeDir = path.join(workspacePath, ".specify", ".runtime");
|
|
93
200
|
if (!fs.existsSync(runtimeDir)) {
|
|
94
201
|
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
95
202
|
}
|
|
96
|
-
fs.writeFileSync(path.join(runtimeDir,
|
|
203
|
+
fs.writeFileSync(path.join(runtimeDir, "phase-done.txt"), `${doneTag}:${exitCode}`, "utf-8");
|
|
97
204
|
}
|
|
98
205
|
catch {
|
|
99
206
|
// ignore
|
|
100
207
|
}
|
|
101
208
|
resolve(exitCode);
|
|
102
209
|
});
|
|
103
|
-
child.on('error', (err) => {
|
|
104
|
-
onData?.(`\nFailed to start process: ${err.message}\n`);
|
|
105
|
-
resolve(-1);
|
|
106
|
-
});
|
|
107
210
|
});
|
|
108
211
|
}
|
|
109
212
|
buildSpawnArgs(config, prompt) {
|
|
110
213
|
const agentType = config.agentType;
|
|
111
214
|
const cliPath = config.agentPath || this.getDefaultCli(agentType);
|
|
112
215
|
switch (agentType) {
|
|
113
|
-
case
|
|
216
|
+
case "claude":
|
|
114
217
|
return {
|
|
115
218
|
cmd: cliPath,
|
|
116
|
-
args: [
|
|
219
|
+
args: ["--permission-mode", "bypassPermissions", prompt],
|
|
117
220
|
};
|
|
118
|
-
case
|
|
221
|
+
case "gemini":
|
|
119
222
|
return {
|
|
120
223
|
cmd: cliPath,
|
|
121
224
|
args: [],
|
|
122
|
-
stdin: prompt
|
|
225
|
+
stdin: prompt,
|
|
123
226
|
};
|
|
124
|
-
case
|
|
227
|
+
case "copilot":
|
|
125
228
|
return {
|
|
126
229
|
cmd: cliPath,
|
|
127
|
-
args: [prompt]
|
|
230
|
+
args: [prompt],
|
|
128
231
|
};
|
|
129
|
-
case
|
|
232
|
+
case "openai":
|
|
130
233
|
return {
|
|
131
234
|
cmd: cliPath,
|
|
132
|
-
args: [
|
|
133
|
-
stdin: prompt
|
|
235
|
+
args: ["exec", "-"],
|
|
236
|
+
stdin: prompt,
|
|
134
237
|
};
|
|
135
|
-
case
|
|
238
|
+
case "custom":
|
|
136
239
|
if (config.customCommand) {
|
|
137
|
-
const parts = config.customCommand.split(
|
|
240
|
+
const parts = config.customCommand.split(" ");
|
|
138
241
|
const cmd = parts[0];
|
|
139
|
-
const args = parts
|
|
242
|
+
const args = parts
|
|
243
|
+
.slice(1)
|
|
244
|
+
.map((arg) => (arg === "{{prompt}}" ? prompt : arg));
|
|
140
245
|
// If {{prompt}} isn't in args, append it
|
|
141
|
-
if (!config.customCommand.includes(
|
|
246
|
+
if (!config.customCommand.includes("{{prompt}}")) {
|
|
142
247
|
args.push(prompt);
|
|
143
248
|
}
|
|
144
249
|
return { cmd, args };
|
|
145
250
|
}
|
|
146
|
-
return { cmd:
|
|
251
|
+
return { cmd: "specify", args: [prompt] };
|
|
147
252
|
default:
|
|
148
253
|
return { cmd: cliPath, args: [prompt] };
|
|
149
254
|
}
|
|
150
255
|
}
|
|
151
256
|
getDefaultCli(agentType) {
|
|
152
257
|
const defaults = {
|
|
153
|
-
claude:
|
|
154
|
-
gemini:
|
|
155
|
-
copilot:
|
|
156
|
-
openai:
|
|
258
|
+
claude: "claude",
|
|
259
|
+
gemini: "gemini",
|
|
260
|
+
copilot: "ghcs",
|
|
261
|
+
openai: "codex",
|
|
157
262
|
};
|
|
158
|
-
return defaults[agentType] ||
|
|
263
|
+
return defaults[agentType] || "specify";
|
|
159
264
|
}
|
|
160
265
|
}
|
|
161
266
|
exports.ProcessAgentRunner = ProcessAgentRunner;
|
|
@@ -61,7 +61,12 @@ class FSWorkspaceRepository {
|
|
|
61
61
|
else {
|
|
62
62
|
state.constitutionPhase.filePath = null;
|
|
63
63
|
state.constitutionPhase.content = null;
|
|
64
|
-
|
|
64
|
+
// Don't clobber explicit user/runtime states when no file is on disk:
|
|
65
|
+
// 'running' (agent still generating) and 'approved' (a stored user decision,
|
|
66
|
+
// e.g. advanced via Kanban drag) must survive reconciliation.
|
|
67
|
+
if (!this.isUserState(state.constitutionPhase.status)) {
|
|
68
|
+
state.constitutionPhase.status = 'idle';
|
|
69
|
+
}
|
|
65
70
|
}
|
|
66
71
|
// 2. Reconcile Features in specs/
|
|
67
72
|
const specsDir = path.join(workspacePath, 'specs');
|
|
@@ -100,7 +105,9 @@ class FSWorkspaceRepository {
|
|
|
100
105
|
else {
|
|
101
106
|
ps.filePath = null;
|
|
102
107
|
ps.content = null;
|
|
103
|
-
ps.status
|
|
108
|
+
if (!this.isUserState(ps.status)) {
|
|
109
|
+
ps.status = 'idle';
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
// Reconcile clarification (clarification.md or clarify.md)
|
|
@@ -118,7 +125,9 @@ class FSWorkspaceRepository {
|
|
|
118
125
|
else {
|
|
119
126
|
clarifyPhase.filePath = null;
|
|
120
127
|
clarifyPhase.content = null;
|
|
121
|
-
clarifyPhase.status
|
|
128
|
+
if (!this.isUserState(clarifyPhase.status)) {
|
|
129
|
+
clarifyPhase.status = 'idle';
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
// Implementation phase doesn't have its own file, but we can resolve its status.
|
|
124
133
|
// If specs/[feature]/tasks.md exists, and all checkboxes are checked, it could be complete.
|
|
@@ -127,6 +136,24 @@ class FSWorkspaceRepository {
|
|
|
127
136
|
// Make sure its filePath is null (since there is no implementation.md file)
|
|
128
137
|
implPhase.filePath = null;
|
|
129
138
|
implPhase.content = null;
|
|
139
|
+
// Reconcile review-gate personas against their report files. Statuses
|
|
140
|
+
// saved by the gate are kept; this is a safety net that also reflects a
|
|
141
|
+
// report written/edited out of band. Never downgrade a 'running' persona.
|
|
142
|
+
if (implPhase.personas) {
|
|
143
|
+
for (const persona of implPhase.personas) {
|
|
144
|
+
const reportFp = path.join(specsDir, feature.name, 'reviews', `${persona.id}.md`);
|
|
145
|
+
if (fs.existsSync(reportFp)) {
|
|
146
|
+
persona.reportPath = path.join('specs', feature.name, 'reviews', `${persona.id}.md`);
|
|
147
|
+
if (persona.status !== 'running') {
|
|
148
|
+
const verdict = fs.readFileSync(reportFp, 'utf-8').toUpperCase();
|
|
149
|
+
if (/VERDICT:\s*FAIL/.test(verdict))
|
|
150
|
+
persona.status = 'failed';
|
|
151
|
+
else if (/VERDICT:\s*PASS/.test(verdict))
|
|
152
|
+
persona.status = 'passed';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
130
157
|
}
|
|
131
158
|
}
|
|
132
159
|
else {
|
|
@@ -156,7 +183,9 @@ class FSWorkspaceRepository {
|
|
|
156
183
|
phases: f.phases.map(p => ({
|
|
157
184
|
phase: p.phase,
|
|
158
185
|
status: p.status,
|
|
159
|
-
stale: p.stale
|
|
186
|
+
stale: p.stale,
|
|
187
|
+
// Persist the implementation review-gate persona statuses.
|
|
188
|
+
...(p.personas ? { personas: p.personas.map(ps => ({ id: ps.id, status: ps.status })) } : {})
|
|
160
189
|
}))
|
|
161
190
|
})),
|
|
162
191
|
activeFeatureName: state.activeFeatureName
|
|
@@ -245,6 +274,9 @@ class FSWorkspaceRepository {
|
|
|
245
274
|
phases: FEATURE_PHASES.map(p => this.makePhaseState(p))
|
|
246
275
|
};
|
|
247
276
|
}
|
|
277
|
+
isUserState(status) {
|
|
278
|
+
return status === 'running' || status === 'approved';
|
|
279
|
+
}
|
|
248
280
|
loadStateFile(workspacePath) {
|
|
249
281
|
const statePath = path.join(workspacePath, '.specify', '.runtime', 'workflow-state.json');
|
|
250
282
|
if (!fs.existsSync(statePath)) {
|
|
@@ -267,7 +299,10 @@ class FSWorkspaceRepository {
|
|
|
267
299
|
status: savedPhase?.status || 'idle',
|
|
268
300
|
stale: savedPhase?.stale || false,
|
|
269
301
|
filePath: null,
|
|
270
|
-
content: null
|
|
302
|
+
content: null,
|
|
303
|
+
...(Array.isArray(savedPhase?.personas)
|
|
304
|
+
? { personas: savedPhase.personas.map((ps) => ({ id: ps.id, status: ps.status || 'idle' })) }
|
|
305
|
+
: {})
|
|
271
306
|
};
|
|
272
307
|
})
|
|
273
308
|
}));
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadPty = loadPty;
|
|
37
|
+
exports._clearPtyCacheForTesting = _clearPtyCacheForTesting;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
let cached = null;
|
|
42
|
+
class PureJsPtyLike {
|
|
43
|
+
child;
|
|
44
|
+
constructor(child) {
|
|
45
|
+
this.child = child;
|
|
46
|
+
}
|
|
47
|
+
write(data) {
|
|
48
|
+
this.child.stdin?.write(data);
|
|
49
|
+
}
|
|
50
|
+
resize(cols, rows) {
|
|
51
|
+
// No-op for standard child_process pipes
|
|
52
|
+
}
|
|
53
|
+
kill(signal) {
|
|
54
|
+
this.child.kill(signal);
|
|
55
|
+
}
|
|
56
|
+
onData(cb) {
|
|
57
|
+
this.child.stdout?.on("data", (chunk) => cb(chunk.toString()));
|
|
58
|
+
this.child.stderr?.on("data", (chunk) => cb(chunk.toString()));
|
|
59
|
+
}
|
|
60
|
+
onExit(cb) {
|
|
61
|
+
this.child.on("exit", (code, signal) => {
|
|
62
|
+
cb({ exitCode: code ?? 0, signal: signal ? 1 : undefined });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const pureJsPtyModule = {
|
|
67
|
+
spawn(file, args, options) {
|
|
68
|
+
const cp = (0, child_process_1.spawn)(file, args, {
|
|
69
|
+
cwd: options.cwd,
|
|
70
|
+
env: options.env,
|
|
71
|
+
});
|
|
72
|
+
return new PureJsPtyLike(cp);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Loads node-pty defensively.
|
|
77
|
+
*
|
|
78
|
+
* node-pty relies on a native `spawn-helper` binary (on POSIX) that must be
|
|
79
|
+
* executable. Some package managers / extraction paths drop the exec bit when
|
|
80
|
+
* unpacking the prebuilt binaries, which makes pty.spawn fail with
|
|
81
|
+
* "posix_spawnp failed". We restore the exec bit before first use so the
|
|
82
|
+
* workspace shell and interactive agent phases work regardless of how the
|
|
83
|
+
* dependency was installed.
|
|
84
|
+
*/
|
|
85
|
+
function loadPty() {
|
|
86
|
+
if (cached)
|
|
87
|
+
return cached;
|
|
88
|
+
if (process.env.SPECKIT_TEST_FORCE_FALLBACK === "true") {
|
|
89
|
+
cached = pureJsPtyModule;
|
|
90
|
+
return pureJsPtyModule;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
94
|
+
const pty = require("node-pty");
|
|
95
|
+
if (process.platform !== "win32") {
|
|
96
|
+
try {
|
|
97
|
+
const ptyDir = path.dirname(require.resolve("node-pty/package.json"));
|
|
98
|
+
const prebuilds = path.join(ptyDir, "prebuilds");
|
|
99
|
+
if (fs.existsSync(prebuilds)) {
|
|
100
|
+
for (const entry of fs.readdirSync(prebuilds)) {
|
|
101
|
+
const helper = path.join(prebuilds, entry, "spawn-helper");
|
|
102
|
+
if (fs.existsSync(helper)) {
|
|
103
|
+
try {
|
|
104
|
+
fs.chmodSync(helper, 0o755);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// best effort
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// best effort — if anything fails we still return the module
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
cached = pty;
|
|
118
|
+
return pty;
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.warn("node-pty failed to load, falling back to pure child_process pty emulation.");
|
|
122
|
+
cached = pureJsPtyModule;
|
|
123
|
+
return pureJsPtyModule;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function _clearPtyCacheForTesting() {
|
|
127
|
+
cached = null;
|
|
128
|
+
}
|