@launch11/srgical 0.0.4 → 0.0.6
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/CHANGELOG.md +11 -0
- package/README.md +28 -7
- package/dist/commands/about.js +32 -0
- package/dist/commands/changelog.js +29 -0
- package/dist/commands/doctor.js +60 -13
- package/dist/commands/init.js +10 -6
- package/dist/commands/run-next.js +41 -8
- package/dist/commands/studio.js +2 -2
- package/dist/commands/version.js +25 -0
- package/dist/core/agent.js +19 -19
- package/dist/core/augment.js +7 -7
- package/dist/core/auto-run-state.js +89 -0
- package/dist/core/auto-run.js +270 -0
- package/dist/core/claude.js +7 -7
- package/dist/core/codex.js +7 -7
- package/dist/core/execution-state.js +5 -5
- package/dist/core/local-pack.js +9 -6
- package/dist/core/package-info.js +61 -0
- package/dist/core/planning-epochs.js +10 -3
- package/dist/core/planning-pack-state.js +121 -9
- package/dist/core/planning-state.js +61 -0
- package/dist/core/prompts.js +4 -4
- package/dist/core/studio-session.js +14 -14
- package/dist/core/templates.js +13 -10
- package/dist/core/workspace.js +127 -8
- package/dist/index.js +48 -17
- package/dist/postinstall.js +37 -0
- package/dist/ui/studio.js +329 -193
- package/package.json +4 -1
- package/postinstall.js +14 -0
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readPlanningPackState = readPlanningPackState;
|
|
4
|
+
exports.isExecutionStepSummary = isExecutionStepSummary;
|
|
5
|
+
exports.isExecutionReadyState = isExecutionReadyState;
|
|
6
|
+
const auto_run_state_1 = require("./auto-run-state");
|
|
4
7
|
const execution_state_1 = require("./execution-state");
|
|
8
|
+
const planning_state_1 = require("./planning-state");
|
|
9
|
+
const studio_session_1 = require("./studio-session");
|
|
5
10
|
const workspace_1 = require("./workspace");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const packPresent = await (0, workspace_1.planningPackExists)(workspaceRoot);
|
|
11
|
+
async function readPlanningPackState(workspaceRoot, options = {}) {
|
|
12
|
+
const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot, options);
|
|
13
|
+
const packPresent = await (0, workspace_1.planningPackExists)(workspaceRoot, options);
|
|
9
14
|
const currentPosition = emptyCurrentPosition();
|
|
10
15
|
let nextStepSummary = null;
|
|
11
16
|
let trackerReadable = false;
|
|
12
17
|
if (packPresent) {
|
|
13
18
|
try {
|
|
14
|
-
const tracker = await (0, workspace_1.readText)(
|
|
19
|
+
const tracker = await (0, workspace_1.readText)(paths.tracker);
|
|
15
20
|
Object.assign(currentPosition, parseCurrentPosition(tracker));
|
|
16
21
|
nextStepSummary = parseNextStepSummary(tracker, currentPosition.nextRecommended);
|
|
17
22
|
trackerReadable = currentPosition.lastCompleted !== null || currentPosition.nextRecommended !== null;
|
|
@@ -20,25 +25,132 @@ async function readPlanningPackState(workspaceRoot) {
|
|
|
20
25
|
trackerReadable = false;
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
|
-
const lastExecution = await
|
|
28
|
+
const [lastExecution, planningState, autoRun, docsPresent, studioSession] = await Promise.all([
|
|
29
|
+
(0, execution_state_1.loadExecutionState)(workspaceRoot, options),
|
|
30
|
+
(0, planning_state_1.loadPlanningState)(workspaceRoot, options),
|
|
31
|
+
(0, auto_run_state_1.loadAutoRunState)(workspaceRoot, options),
|
|
32
|
+
countPresentDocs(paths),
|
|
33
|
+
(0, studio_session_1.loadStudioSessionState)(workspaceRoot, options)
|
|
34
|
+
]);
|
|
35
|
+
const readiness = buildReadiness(studioSession.messages, nextStepSummary);
|
|
36
|
+
const packMode = planningState?.packMode ?? (0, planning_state_1.inferLegacyPackMode)(currentPosition);
|
|
37
|
+
const executionActivated = Boolean(lastExecution ||
|
|
38
|
+
(autoRun && autoRun.status !== "idle") ||
|
|
39
|
+
(isExecutionStepSummary(nextStepSummary) && currentPosition.lastCompleted !== "PLAN-001"));
|
|
40
|
+
const mode = derivePlanningMode({
|
|
41
|
+
packPresent,
|
|
42
|
+
packMode,
|
|
43
|
+
readiness,
|
|
44
|
+
nextStepSummary,
|
|
45
|
+
autoRun,
|
|
46
|
+
executionActivated
|
|
47
|
+
});
|
|
24
48
|
return {
|
|
49
|
+
planId: paths.planId,
|
|
50
|
+
packDir: paths.relativeDir,
|
|
25
51
|
packPresent,
|
|
26
52
|
trackerReadable,
|
|
53
|
+
docsPresent,
|
|
27
54
|
currentPosition,
|
|
28
55
|
nextStepSummary,
|
|
29
|
-
lastExecution
|
|
56
|
+
lastExecution,
|
|
57
|
+
planningState,
|
|
58
|
+
packMode,
|
|
59
|
+
readiness,
|
|
60
|
+
autoRun,
|
|
61
|
+
executionActivated,
|
|
62
|
+
mode,
|
|
63
|
+
hasFailureOverlay: lastExecution?.status === "failure"
|
|
30
64
|
};
|
|
31
65
|
}
|
|
66
|
+
function isExecutionStepSummary(step) {
|
|
67
|
+
if (!step) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const phase = step.phase?.toLowerCase() ?? "";
|
|
71
|
+
return phase.includes("delivery") || phase.includes("execution") || /^exec[-\d]/i.test(step.id);
|
|
72
|
+
}
|
|
73
|
+
function isExecutionReadyState(state) {
|
|
74
|
+
return state.packPresent && state.packMode === "authored" && isExecutionStepSummary(state.nextStepSummary);
|
|
75
|
+
}
|
|
76
|
+
function derivePlanningMode(input) {
|
|
77
|
+
if (!input.packPresent) {
|
|
78
|
+
return "No Pack";
|
|
79
|
+
}
|
|
80
|
+
if (input.autoRun?.status === "running" || input.autoRun?.status === "stop_requested") {
|
|
81
|
+
return "Auto Running";
|
|
82
|
+
}
|
|
83
|
+
if (input.packMode === "scaffolded") {
|
|
84
|
+
return input.readiness.readyToWrite ? "Ready to Write" : "Gathering Context";
|
|
85
|
+
}
|
|
86
|
+
if (!isExecutionStepSummary(input.nextStepSummary)) {
|
|
87
|
+
return "Plan Written - Needs Step";
|
|
88
|
+
}
|
|
89
|
+
return input.executionActivated ? "Execution Active" : "Ready to Execute";
|
|
90
|
+
}
|
|
91
|
+
function buildReadiness(messages, nextStepSummary) {
|
|
92
|
+
const meaningfulMessages = messages.filter((message) => message.content.trim().length > 0);
|
|
93
|
+
const userMessages = meaningfulMessages.filter((message) => message.role === "user");
|
|
94
|
+
const assistantMessages = meaningfulMessages.filter((message) => message.role === "assistant" &&
|
|
95
|
+
!studio_session_1.DEFAULT_STUDIO_MESSAGES.some((defaultMessage) => defaultMessage.content === message.content));
|
|
96
|
+
const transcript = meaningfulMessages.map((message) => message.content.toLowerCase()).join("\n");
|
|
97
|
+
const checks = [
|
|
98
|
+
{
|
|
99
|
+
id: "goal",
|
|
100
|
+
label: "Goal captured",
|
|
101
|
+
passed: userMessages.length > 0
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "repo",
|
|
105
|
+
label: "Repo context captured",
|
|
106
|
+
passed: /repo|current|existing|already|codebase|today|currently|workspace/.test(transcript)
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "constraints",
|
|
110
|
+
label: "Constraints or decisions captured",
|
|
111
|
+
passed: /constraint|must|should|can't|cannot|need|require|locked|decision|prefer|non-negotiable/.test(transcript)
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "execution",
|
|
115
|
+
label: "First executable slice captured",
|
|
116
|
+
passed: isExecutionStepSummary(nextStepSummary) || /step|slice|execute|execution|implement|delivery|tracker/.test(transcript)
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
const score = checks.filter((check) => check.passed).length;
|
|
120
|
+
const readyToWrite = score >= 3 && userMessages.length > 0 && assistantMessages.length > 0;
|
|
121
|
+
return {
|
|
122
|
+
checks,
|
|
123
|
+
score,
|
|
124
|
+
total: checks.length,
|
|
125
|
+
readyToWrite,
|
|
126
|
+
missingLabels: checks.filter((check) => !check.passed).map((check) => check.label)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function countPresentDocs(paths) {
|
|
130
|
+
const checks = await Promise.all([
|
|
131
|
+
(0, workspace_1.fileExists)(paths.plan),
|
|
132
|
+
(0, workspace_1.fileExists)(paths.context),
|
|
133
|
+
(0, workspace_1.fileExists)(paths.tracker),
|
|
134
|
+
(0, workspace_1.fileExists)(paths.nextPrompt)
|
|
135
|
+
]);
|
|
136
|
+
return checks.filter(Boolean).length;
|
|
137
|
+
}
|
|
32
138
|
function parseCurrentPosition(tracker) {
|
|
33
139
|
return {
|
|
34
140
|
lastCompleted: readCurrentPositionValue(tracker, "Last Completed"),
|
|
35
|
-
nextRecommended: readCurrentPositionValue(tracker, "Next Recommended"),
|
|
141
|
+
nextRecommended: normalizeStepReference(readCurrentPositionValue(tracker, "Next Recommended")),
|
|
36
142
|
updatedAt: readCurrentPositionValue(tracker, "Updated At")
|
|
37
143
|
};
|
|
38
144
|
}
|
|
39
145
|
function readCurrentPositionValue(tracker, label) {
|
|
40
|
-
const match = tracker.match(new RegExp(`- ${escapeRegExp(label)}:
|
|
41
|
-
return match?.[1] ?? null;
|
|
146
|
+
const match = tracker.match(new RegExp(`- ${escapeRegExp(label)}: (?:\\\`([^\\\`]+)\\\`|([^\\n]+))`));
|
|
147
|
+
return match?.[1]?.trim() ?? match?.[2]?.trim() ?? null;
|
|
148
|
+
}
|
|
149
|
+
function normalizeStepReference(value) {
|
|
150
|
+
if (!value) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return value.toLowerCase() === "none queued" ? null : value;
|
|
42
154
|
}
|
|
43
155
|
function escapeRegExp(value) {
|
|
44
156
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadPlanningState = loadPlanningState;
|
|
4
|
+
exports.savePlanningState = savePlanningState;
|
|
5
|
+
exports.markPlanningPackAuthored = markPlanningPackAuthored;
|
|
6
|
+
exports.ensurePlanningPackState = ensurePlanningPackState;
|
|
7
|
+
exports.inferLegacyPackMode = inferLegacyPackMode;
|
|
8
|
+
const workspace_1 = require("./workspace");
|
|
9
|
+
async function loadPlanningState(workspaceRoot, options = {}) {
|
|
10
|
+
const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot, options);
|
|
11
|
+
if (!(await (0, workspace_1.fileExists)(paths.planningState))) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(await (0, workspace_1.readText)(paths.planningState));
|
|
16
|
+
if (parsed.version !== 1 ||
|
|
17
|
+
typeof parsed.planId !== "string" ||
|
|
18
|
+
typeof parsed.createdAt !== "string" ||
|
|
19
|
+
typeof parsed.updatedAt !== "string" ||
|
|
20
|
+
(parsed.packMode !== "scaffolded" && parsed.packMode !== "authored")) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
version: 1,
|
|
25
|
+
planId: parsed.planId,
|
|
26
|
+
createdAt: parsed.createdAt,
|
|
27
|
+
updatedAt: parsed.updatedAt,
|
|
28
|
+
packMode: parsed.packMode
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function savePlanningState(workspaceRoot, packMode, options = {}) {
|
|
36
|
+
const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot, options);
|
|
37
|
+
const existing = await loadPlanningState(workspaceRoot, options);
|
|
38
|
+
const now = new Date().toISOString();
|
|
39
|
+
const state = {
|
|
40
|
+
version: 1,
|
|
41
|
+
planId: paths.planId,
|
|
42
|
+
createdAt: existing?.createdAt ?? now,
|
|
43
|
+
updatedAt: now,
|
|
44
|
+
packMode
|
|
45
|
+
};
|
|
46
|
+
await (0, workspace_1.writeText)(paths.planningState, JSON.stringify(state, null, 2));
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
async function markPlanningPackAuthored(workspaceRoot, options = {}) {
|
|
50
|
+
return savePlanningState(workspaceRoot, "authored", options);
|
|
51
|
+
}
|
|
52
|
+
async function ensurePlanningPackState(workspaceRoot, packMode, options = {}) {
|
|
53
|
+
const existing = await loadPlanningState(workspaceRoot, options);
|
|
54
|
+
if (existing) {
|
|
55
|
+
return existing;
|
|
56
|
+
}
|
|
57
|
+
return savePlanningState(workspaceRoot, packMode, options);
|
|
58
|
+
}
|
|
59
|
+
function inferLegacyPackMode(position) {
|
|
60
|
+
return position.lastCompleted === "BOOT-001" && position.nextRecommended === "PLAN-001" ? "scaffolded" : "authored";
|
|
61
|
+
}
|
package/dist/core/prompts.js
CHANGED
|
@@ -38,8 +38,8 @@ ${renderTranscript(messages)}
|
|
|
38
38
|
|
|
39
39
|
Respond as the planning partner.`;
|
|
40
40
|
}
|
|
41
|
-
async function buildPackWriterPrompt(messages, workspaceRoot) {
|
|
42
|
-
const repoTruth = await buildRepoTruthSnapshot(workspaceRoot);
|
|
41
|
+
async function buildPackWriterPrompt(messages, workspaceRoot, options = {}) {
|
|
42
|
+
const repoTruth = await buildRepoTruthSnapshot(workspaceRoot, options);
|
|
43
43
|
return `You are writing a planning pack for the current repository.
|
|
44
44
|
|
|
45
45
|
Read the conversation transcript below and update or create the following files under .srgical/:
|
|
@@ -74,8 +74,8 @@ Conversation transcript:
|
|
|
74
74
|
${renderTranscript(messages)}
|
|
75
75
|
`;
|
|
76
76
|
}
|
|
77
|
-
async function buildRepoTruthSnapshot(workspaceRoot) {
|
|
78
|
-
const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot);
|
|
77
|
+
async function buildRepoTruthSnapshot(workspaceRoot, options = {}) {
|
|
78
|
+
const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot, options);
|
|
79
79
|
const [packageSummary, topLevelEntries, sourceFiles, docFiles, readmeSnippet, foundationSnippet, adrSnippet, planSnippet, contextSnippet, trackerSnippet, nextPromptSnippet] = await Promise.all([
|
|
80
80
|
summarizePackageManifest(workspaceRoot),
|
|
81
81
|
listDirectoryEntries(workspaceRoot),
|
|
@@ -13,11 +13,11 @@ exports.DEFAULT_STUDIO_MESSAGES = [
|
|
|
13
13
|
content: "Describe what you are building, what is already true in the repo, or the next decision you need to make. I will help turn it into a disciplined `.srgical/` execution pack, and `/write` will update the repo files when you are ready."
|
|
14
14
|
}
|
|
15
15
|
];
|
|
16
|
-
async function loadStudioSession(workspaceRoot) {
|
|
17
|
-
return (await loadStudioSessionState(workspaceRoot)).messages;
|
|
16
|
+
async function loadStudioSession(workspaceRoot, options = {}) {
|
|
17
|
+
return (await loadStudioSessionState(workspaceRoot, options)).messages;
|
|
18
18
|
}
|
|
19
|
-
async function loadStudioSessionState(workspaceRoot) {
|
|
20
|
-
const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot);
|
|
19
|
+
async function loadStudioSessionState(workspaceRoot, options = {}) {
|
|
20
|
+
const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot, options);
|
|
21
21
|
const exists = await (0, workspace_1.fileExists)(paths.studioSession);
|
|
22
22
|
if (!exists) {
|
|
23
23
|
return createDefaultSessionState();
|
|
@@ -35,25 +35,25 @@ async function loadStudioSessionState(workspaceRoot) {
|
|
|
35
35
|
return createDefaultSessionState();
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
async function saveStudioSession(workspaceRoot, messages) {
|
|
39
|
-
const currentState = await loadStudioSessionState(workspaceRoot);
|
|
38
|
+
async function saveStudioSession(workspaceRoot, messages, options = {}) {
|
|
39
|
+
const currentState = await loadStudioSessionState(workspaceRoot, options);
|
|
40
40
|
await writeStudioSession(workspaceRoot, {
|
|
41
41
|
messages,
|
|
42
42
|
activeAgentId: currentState.activeAgentId
|
|
43
|
-
});
|
|
43
|
+
}, options);
|
|
44
44
|
}
|
|
45
|
-
async function loadStoredActiveAgentId(workspaceRoot) {
|
|
46
|
-
return (await loadStudioSessionState(workspaceRoot)).activeAgentId;
|
|
45
|
+
async function loadStoredActiveAgentId(workspaceRoot, options = {}) {
|
|
46
|
+
return (await loadStudioSessionState(workspaceRoot, options)).activeAgentId;
|
|
47
47
|
}
|
|
48
|
-
async function saveStoredActiveAgentId(workspaceRoot, activeAgentId) {
|
|
49
|
-
const currentState = await loadStudioSessionState(workspaceRoot);
|
|
48
|
+
async function saveStoredActiveAgentId(workspaceRoot, activeAgentId, options = {}) {
|
|
49
|
+
const currentState = await loadStudioSessionState(workspaceRoot, options);
|
|
50
50
|
await writeStudioSession(workspaceRoot, {
|
|
51
51
|
messages: currentState.messages,
|
|
52
52
|
activeAgentId
|
|
53
|
-
});
|
|
53
|
+
}, options);
|
|
54
54
|
}
|
|
55
|
-
async function writeStudioSession(workspaceRoot, state) {
|
|
56
|
-
const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot);
|
|
55
|
+
async function writeStudioSession(workspaceRoot, state, options = {}) {
|
|
56
|
+
const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot, options);
|
|
57
57
|
const payload = {
|
|
58
58
|
version: 2,
|
|
59
59
|
updatedAt: new Date().toISOString(),
|
package/dist/core/templates.js
CHANGED
|
@@ -56,7 +56,8 @@ Define and ship a local-first CLI that helps a user:
|
|
|
56
56
|
- the UI feels like a sharp creative control room
|
|
57
57
|
`;
|
|
58
58
|
}
|
|
59
|
-
function buildContextTemplate() {
|
|
59
|
+
function buildContextTemplate(paths) {
|
|
60
|
+
const planDir = `\`${paths.relativeDir}/\``;
|
|
60
61
|
return `# Agent Context Kickoff
|
|
61
62
|
|
|
62
63
|
Updated: ${new Date().toISOString()}
|
|
@@ -64,7 +65,7 @@ Updated By: srgical
|
|
|
64
65
|
|
|
65
66
|
## Mission
|
|
66
67
|
|
|
67
|
-
Continue the current project from the planning pack in
|
|
68
|
+
Continue the current project from the planning pack in ${planDir}. Read the stable plan, the tracker, and the next
|
|
68
69
|
agent prompt before making changes.
|
|
69
70
|
|
|
70
71
|
## Working Agreements
|
|
@@ -86,6 +87,7 @@ agent prompt before making changes.
|
|
|
86
87
|
### ${new Date().toISOString().slice(0, 10)} - BOOT-001 - srgical
|
|
87
88
|
|
|
88
89
|
- Created the initial \`.srgical/\` planning pack.
|
|
90
|
+
- Active planning directory: \`${paths.relativeDir}\`.
|
|
89
91
|
- Validation: confirmed the four planning-pack files were written.
|
|
90
92
|
- Blockers: none.
|
|
91
93
|
- Next recommended work: \`PLAN-001\`.
|
|
@@ -138,7 +140,8 @@ Updated By: srgical
|
|
|
138
140
|
| EXEC-001 | pending | PLAN-001 | Execute the next eligible implementation slice from the tracker. | The selected slice is complete, validated, and logged. | Pending tracker detail. |
|
|
139
141
|
`;
|
|
140
142
|
}
|
|
141
|
-
function buildNextPromptTemplate() {
|
|
143
|
+
function buildNextPromptTemplate(paths) {
|
|
144
|
+
const planDir = paths.relativeDir;
|
|
142
145
|
return `# Next Agent Prompt
|
|
143
146
|
|
|
144
147
|
You are continuing the current project from the existing repo state. Do not restart product design or casually rewrite
|
|
@@ -146,9 +149,9 @@ the whole codebase.
|
|
|
146
149
|
|
|
147
150
|
## Read Order
|
|
148
151
|
|
|
149
|
-
1. Read
|
|
150
|
-
2. Read
|
|
151
|
-
3. Read
|
|
152
|
+
1. Read \`${planDir}/02-agent-context-kickoff.md\`.
|
|
153
|
+
2. Read \`${planDir}/01-product-plan.md\`.
|
|
154
|
+
3. Read \`${planDir}/03-detailed-implementation-plan.md\`.
|
|
152
155
|
4. Execute only the next eligible step block.
|
|
153
156
|
|
|
154
157
|
## What To Determine Before Editing
|
|
@@ -167,10 +170,10 @@ the whole codebase.
|
|
|
167
170
|
|
|
168
171
|
## Required Updates After Execution
|
|
169
172
|
|
|
170
|
-
1. Update
|
|
173
|
+
1. Update \`${planDir}/03-detailed-implementation-plan.md\`.
|
|
171
174
|
2. Mark finished steps \`done\` only if validation passed.
|
|
172
175
|
3. Update the \`Current Position\` section.
|
|
173
|
-
4. Append a dated handoff entry to
|
|
176
|
+
4. Append a dated handoff entry to \`${planDir}/02-agent-context-kickoff.md\`.
|
|
174
177
|
|
|
175
178
|
## Stop Conditions
|
|
176
179
|
|
|
@@ -182,8 +185,8 @@ the whole codebase.
|
|
|
182
185
|
function getInitialTemplates(paths) {
|
|
183
186
|
return {
|
|
184
187
|
[paths.plan]: buildPlanTemplate(paths.root),
|
|
185
|
-
[paths.context]: buildContextTemplate(),
|
|
188
|
+
[paths.context]: buildContextTemplate(paths),
|
|
186
189
|
[paths.tracker]: buildTrackerTemplate(),
|
|
187
|
-
[paths.nextPrompt]: buildNextPromptTemplate()
|
|
190
|
+
[paths.nextPrompt]: buildNextPromptTemplate(paths)
|
|
188
191
|
};
|
|
189
192
|
}
|
package/dist/core/workspace.js
CHANGED
|
@@ -3,40 +3,78 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PLAN_DIR = void 0;
|
|
6
|
+
exports.ACTIVE_PLAN_FILE = exports.NAMED_PLANS_DIR = exports.DEFAULT_PLAN_ID = exports.PLAN_DIR = void 0;
|
|
7
7
|
exports.resolveWorkspace = resolveWorkspace;
|
|
8
|
+
exports.normalizePlanId = normalizePlanId;
|
|
9
|
+
exports.getPlanningRoot = getPlanningRoot;
|
|
8
10
|
exports.getPlanningPackPaths = getPlanningPackPaths;
|
|
9
11
|
exports.ensurePlanningDir = ensurePlanningDir;
|
|
12
|
+
exports.ensurePlanningRoot = ensurePlanningRoot;
|
|
10
13
|
exports.fileExists = fileExists;
|
|
11
14
|
exports.planningPackExists = planningPackExists;
|
|
15
|
+
exports.listPlanningDirectories = listPlanningDirectories;
|
|
16
|
+
exports.readActivePlanId = readActivePlanId;
|
|
17
|
+
exports.saveActivePlanId = saveActivePlanId;
|
|
18
|
+
exports.resolvePlanId = resolvePlanId;
|
|
12
19
|
exports.readText = readText;
|
|
13
20
|
exports.writeText = writeText;
|
|
14
21
|
exports.isGitRepo = isGitRepo;
|
|
15
22
|
const promises_1 = require("node:fs/promises");
|
|
16
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
24
|
exports.PLAN_DIR = ".srgical";
|
|
25
|
+
exports.DEFAULT_PLAN_ID = "default";
|
|
26
|
+
exports.NAMED_PLANS_DIR = "plans";
|
|
27
|
+
exports.ACTIVE_PLAN_FILE = "active-plan.txt";
|
|
18
28
|
function resolveWorkspace(input) {
|
|
19
29
|
return node_path_1.default.resolve(input ?? process.cwd());
|
|
20
30
|
}
|
|
21
|
-
function
|
|
22
|
-
const
|
|
31
|
+
function normalizePlanId(value) {
|
|
32
|
+
const normalized = (value ?? exports.DEFAULT_PLAN_ID)
|
|
33
|
+
.trim()
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
36
|
+
.replace(/^-+|-+$/g, "");
|
|
37
|
+
return normalized.length > 0 ? normalized : exports.DEFAULT_PLAN_ID;
|
|
38
|
+
}
|
|
39
|
+
function getPlanningRoot(root) {
|
|
40
|
+
return node_path_1.default.join(root, exports.PLAN_DIR);
|
|
41
|
+
}
|
|
42
|
+
function getPlanningPackPaths(root, options = {}) {
|
|
43
|
+
const planningRoot = getPlanningRoot(root);
|
|
44
|
+
const planId = normalizePlanId(options.planId);
|
|
45
|
+
const isDefaultPlan = planId === exports.DEFAULT_PLAN_ID;
|
|
46
|
+
const dir = isDefaultPlan ? planningRoot : node_path_1.default.join(planningRoot, exports.NAMED_PLANS_DIR, planId);
|
|
47
|
+
const relativeDir = node_path_1.default.relative(root, dir).replace(/\\/g, "/") || exports.PLAN_DIR;
|
|
23
48
|
return {
|
|
24
49
|
root,
|
|
50
|
+
planningRoot,
|
|
51
|
+
planId,
|
|
52
|
+
isDefaultPlan,
|
|
25
53
|
dir,
|
|
54
|
+
relativeDir,
|
|
26
55
|
plan: node_path_1.default.join(dir, "01-product-plan.md"),
|
|
27
56
|
context: node_path_1.default.join(dir, "02-agent-context-kickoff.md"),
|
|
28
57
|
tracker: node_path_1.default.join(dir, "03-detailed-implementation-plan.md"),
|
|
29
58
|
nextPrompt: node_path_1.default.join(dir, "04-next-agent-prompt.md"),
|
|
30
59
|
studioSession: node_path_1.default.join(dir, "studio-session.json"),
|
|
31
60
|
executionState: node_path_1.default.join(dir, "execution-state.json"),
|
|
32
|
-
executionLog: node_path_1.default.join(dir, "execution-log.md")
|
|
61
|
+
executionLog: node_path_1.default.join(dir, "execution-log.md"),
|
|
62
|
+
planningState: node_path_1.default.join(dir, "planning-state.json"),
|
|
63
|
+
autoRunState: node_path_1.default.join(dir, "auto-run-state.json"),
|
|
64
|
+
activePlanFile: node_path_1.default.join(planningRoot, exports.ACTIVE_PLAN_FILE)
|
|
33
65
|
};
|
|
34
66
|
}
|
|
35
|
-
async function ensurePlanningDir(root) {
|
|
36
|
-
const paths = getPlanningPackPaths(root);
|
|
67
|
+
async function ensurePlanningDir(root, options = {}) {
|
|
68
|
+
const paths = getPlanningPackPaths(root, options);
|
|
69
|
+
await (0, promises_1.mkdir)(paths.planningRoot, { recursive: true });
|
|
37
70
|
await (0, promises_1.mkdir)(paths.dir, { recursive: true });
|
|
38
71
|
return paths;
|
|
39
72
|
}
|
|
73
|
+
async function ensurePlanningRoot(root) {
|
|
74
|
+
const planningRoot = getPlanningRoot(root);
|
|
75
|
+
await (0, promises_1.mkdir)(planningRoot, { recursive: true });
|
|
76
|
+
return planningRoot;
|
|
77
|
+
}
|
|
40
78
|
async function fileExists(filePath) {
|
|
41
79
|
try {
|
|
42
80
|
await (0, promises_1.access)(filePath);
|
|
@@ -46,8 +84,8 @@ async function fileExists(filePath) {
|
|
|
46
84
|
return false;
|
|
47
85
|
}
|
|
48
86
|
}
|
|
49
|
-
async function planningPackExists(root) {
|
|
50
|
-
const paths = getPlanningPackPaths(root);
|
|
87
|
+
async function planningPackExists(root, options = {}) {
|
|
88
|
+
const paths = getPlanningPackPaths(root, options);
|
|
51
89
|
const checks = await Promise.all([
|
|
52
90
|
fileExists(paths.plan),
|
|
53
91
|
fileExists(paths.context),
|
|
@@ -56,6 +94,72 @@ async function planningPackExists(root) {
|
|
|
56
94
|
]);
|
|
57
95
|
return checks.every(Boolean);
|
|
58
96
|
}
|
|
97
|
+
async function listPlanningDirectories(root) {
|
|
98
|
+
const planningRoot = getPlanningRoot(root);
|
|
99
|
+
const namedPlansRoot = node_path_1.default.join(planningRoot, exports.NAMED_PLANS_DIR);
|
|
100
|
+
const planIds = new Set();
|
|
101
|
+
if (await hasDefaultPlanPresence(root)) {
|
|
102
|
+
planIds.add(exports.DEFAULT_PLAN_ID);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const entries = await (0, promises_1.readdir)(namedPlansRoot, { withFileTypes: true });
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (!entry.isDirectory()) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
planIds.add(normalizePlanId(entry.name));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// ignore missing named-plan directory
|
|
115
|
+
}
|
|
116
|
+
const activePlanId = await readActivePlanId(root);
|
|
117
|
+
if (activePlanId) {
|
|
118
|
+
planIds.add(activePlanId);
|
|
119
|
+
}
|
|
120
|
+
return Array.from(planIds)
|
|
121
|
+
.sort((left, right) => {
|
|
122
|
+
if (left === exports.DEFAULT_PLAN_ID) {
|
|
123
|
+
return -1;
|
|
124
|
+
}
|
|
125
|
+
if (right === exports.DEFAULT_PLAN_ID) {
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
return left.localeCompare(right);
|
|
129
|
+
})
|
|
130
|
+
.map((planId) => {
|
|
131
|
+
const paths = getPlanningPackPaths(root, { planId });
|
|
132
|
+
return {
|
|
133
|
+
planId,
|
|
134
|
+
dir: paths.dir,
|
|
135
|
+
relativeDir: paths.relativeDir,
|
|
136
|
+
isDefaultPlan: paths.isDefaultPlan
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function readActivePlanId(root) {
|
|
141
|
+
const markerPath = getPlanningPackPaths(root).activePlanFile;
|
|
142
|
+
if (!(await fileExists(markerPath))) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return normalizePlanId(await readText(markerPath));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function saveActivePlanId(root, planId) {
|
|
153
|
+
const normalizedPlanId = normalizePlanId(planId);
|
|
154
|
+
const planningRoot = await ensurePlanningRoot(root);
|
|
155
|
+
await writeText(node_path_1.default.join(planningRoot, exports.ACTIVE_PLAN_FILE), normalizedPlanId);
|
|
156
|
+
}
|
|
157
|
+
async function resolvePlanId(root, requestedPlanId) {
|
|
158
|
+
if (requestedPlanId && requestedPlanId.trim().length > 0) {
|
|
159
|
+
return normalizePlanId(requestedPlanId);
|
|
160
|
+
}
|
|
161
|
+
return (await readActivePlanId(root)) ?? exports.DEFAULT_PLAN_ID;
|
|
162
|
+
}
|
|
59
163
|
async function readText(filePath) {
|
|
60
164
|
return (0, promises_1.readFile)(filePath, "utf8");
|
|
61
165
|
}
|
|
@@ -65,3 +169,18 @@ async function writeText(filePath, content) {
|
|
|
65
169
|
async function isGitRepo(root) {
|
|
66
170
|
return fileExists(node_path_1.default.join(root, ".git"));
|
|
67
171
|
}
|
|
172
|
+
async function hasDefaultPlanPresence(root) {
|
|
173
|
+
const paths = getPlanningPackPaths(root, { planId: exports.DEFAULT_PLAN_ID });
|
|
174
|
+
const checks = await Promise.all([
|
|
175
|
+
fileExists(paths.plan),
|
|
176
|
+
fileExists(paths.context),
|
|
177
|
+
fileExists(paths.tracker),
|
|
178
|
+
fileExists(paths.nextPrompt),
|
|
179
|
+
fileExists(paths.studioSession),
|
|
180
|
+
fileExists(paths.executionState),
|
|
181
|
+
fileExists(paths.executionLog),
|
|
182
|
+
fileExists(paths.planningState),
|
|
183
|
+
fileExists(paths.autoRunState)
|
|
184
|
+
]);
|
|
185
|
+
return checks.some(Boolean);
|
|
186
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,43 +1,67 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const node_fs_1 = require("node:fs");
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
4
|
const commander_1 = require("commander");
|
|
5
|
+
const about_1 = require("./commands/about");
|
|
6
|
+
const changelog_1 = require("./commands/changelog");
|
|
10
7
|
const doctor_1 = require("./commands/doctor");
|
|
11
8
|
const init_1 = require("./commands/init");
|
|
12
9
|
const run_next_1 = require("./commands/run-next");
|
|
13
10
|
const studio_1 = require("./commands/studio");
|
|
11
|
+
const version_1 = require("./commands/version");
|
|
12
|
+
const package_info_1 = require("./core/package-info");
|
|
14
13
|
const program = new commander_1.Command();
|
|
15
|
-
const
|
|
14
|
+
const packageInfo = (0, package_info_1.readInstalledPackageInfo)();
|
|
15
|
+
if (isStandaloneVersionRequest(process.argv.slice(2))) {
|
|
16
|
+
(0, version_1.runVersionCommand)();
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
16
19
|
program
|
|
17
20
|
.name("srgical")
|
|
18
21
|
.description("Local-first AI planning and execution orchestration.")
|
|
19
|
-
.version(
|
|
22
|
+
.version(packageInfo.version, "-V, --version", "Show installed version and release info.");
|
|
23
|
+
program
|
|
24
|
+
.command("version")
|
|
25
|
+
.description("Show installed version and release info.")
|
|
26
|
+
.action(() => {
|
|
27
|
+
(0, version_1.runVersionCommand)();
|
|
28
|
+
});
|
|
29
|
+
program
|
|
30
|
+
.command("about")
|
|
31
|
+
.description("Show package, release, and supported-agent information.")
|
|
32
|
+
.action(() => {
|
|
33
|
+
(0, about_1.runAboutCommand)();
|
|
34
|
+
});
|
|
35
|
+
program
|
|
36
|
+
.command("changelog")
|
|
37
|
+
.description("Show where to find upgrade notes for the installed version.")
|
|
38
|
+
.action(() => {
|
|
39
|
+
(0, changelog_1.runChangelogCommand)();
|
|
40
|
+
});
|
|
20
41
|
program
|
|
21
42
|
.command("doctor")
|
|
22
43
|
.description("Inspect the workspace and local agent availability.")
|
|
23
44
|
.argument("[workspace]", "Workspace path")
|
|
24
|
-
.
|
|
25
|
-
|
|
45
|
+
.option("--plan <id>", "Planning pack id to inspect")
|
|
46
|
+
.action(async (workspace, options) => {
|
|
47
|
+
await (0, doctor_1.runDoctorCommand)(workspace, { planId: options.plan });
|
|
26
48
|
});
|
|
27
49
|
program
|
|
28
50
|
.command("init")
|
|
29
51
|
.description("Create a local .srgical planning pack scaffold.")
|
|
30
52
|
.argument("[workspace]", "Workspace path")
|
|
31
53
|
.option("-f, --force", "Overwrite an existing planning pack")
|
|
54
|
+
.option("--plan <id>", "Named planning pack id to create or overwrite")
|
|
32
55
|
.action(async (workspace, options) => {
|
|
33
|
-
await (0, init_1.runInitCommand)(workspace, Boolean(options.force));
|
|
56
|
+
await (0, init_1.runInitCommand)(workspace, Boolean(options.force), options.plan);
|
|
34
57
|
});
|
|
35
58
|
program
|
|
36
59
|
.command("studio")
|
|
37
60
|
.description("Open the planning studio.")
|
|
38
61
|
.argument("[workspace]", "Workspace path")
|
|
39
|
-
.
|
|
40
|
-
|
|
62
|
+
.option("--plan <id>", "Planning pack id to open")
|
|
63
|
+
.action(async (workspace, options) => {
|
|
64
|
+
await (0, studio_1.runStudioCommand)(workspace, { planId: options.plan });
|
|
41
65
|
});
|
|
42
66
|
program
|
|
43
67
|
.command("run-next")
|
|
@@ -45,16 +69,23 @@ program
|
|
|
45
69
|
.argument("[workspace]", "Workspace path")
|
|
46
70
|
.option("--dry-run", "Preview the current execution prompt without invoking the active agent")
|
|
47
71
|
.option("--agent <id>", "Temporarily override the active agent for this run only")
|
|
72
|
+
.option("--plan <id>", "Planning pack id to execute")
|
|
73
|
+
.option("--auto", "Continue executing eligible execution steps until a stop condition is reached")
|
|
74
|
+
.option("--max-steps <n>", "Maximum number of auto iterations to attempt", Number)
|
|
48
75
|
.action(async (workspace, options) => {
|
|
49
|
-
await (0, run_next_1.runRunNextCommand)(workspace, {
|
|
76
|
+
await (0, run_next_1.runRunNextCommand)(workspace, {
|
|
77
|
+
dryRun: Boolean(options.dryRun),
|
|
78
|
+
agent: options.agent,
|
|
79
|
+
planId: options.plan,
|
|
80
|
+
auto: Boolean(options.auto),
|
|
81
|
+
maxSteps: options.maxSteps
|
|
82
|
+
});
|
|
50
83
|
});
|
|
51
84
|
program.parseAsync(process.argv).catch((error) => {
|
|
52
85
|
const message = error instanceof Error ? error.message : String(error);
|
|
53
86
|
process.stderr.write(`${message}\n`);
|
|
54
87
|
process.exitCode = 1;
|
|
55
88
|
});
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, "utf8"));
|
|
59
|
-
return packageJson.version ?? "0.0.0";
|
|
89
|
+
function isStandaloneVersionRequest(args) {
|
|
90
|
+
return args.length === 1 && (args[0] === "--version" || args[0] === "-V");
|
|
60
91
|
}
|