@launch11/srgical 0.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.
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadExecutionState = loadExecutionState;
4
+ exports.saveExecutionState = saveExecutionState;
5
+ exports.appendExecutionLog = appendExecutionLog;
6
+ const promises_1 = require("node:fs/promises");
7
+ const workspace_1 = require("./workspace");
8
+ async function loadExecutionState(workspaceRoot) {
9
+ const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot);
10
+ const exists = await (0, workspace_1.fileExists)(paths.executionState);
11
+ if (!exists) {
12
+ return null;
13
+ }
14
+ try {
15
+ const raw = await (0, workspace_1.readText)(paths.executionState);
16
+ const parsed = JSON.parse(raw);
17
+ if (parsed.version !== 1 ||
18
+ (parsed.status !== "success" && parsed.status !== "failure") ||
19
+ (parsed.source !== "studio" && parsed.source !== "run-next") ||
20
+ typeof parsed.updatedAt !== "string" ||
21
+ typeof parsed.summary !== "string") {
22
+ return null;
23
+ }
24
+ return {
25
+ version: 1,
26
+ updatedAt: parsed.updatedAt,
27
+ status: parsed.status,
28
+ source: parsed.source,
29
+ summary: parsed.summary
30
+ };
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function saveExecutionState(workspaceRoot, status, source, summary) {
37
+ const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot);
38
+ const payload = {
39
+ version: 1,
40
+ updatedAt: new Date().toISOString(),
41
+ status,
42
+ source,
43
+ summary: normalizeSummary(summary)
44
+ };
45
+ await (0, workspace_1.writeText)(paths.executionState, JSON.stringify(payload, null, 2));
46
+ }
47
+ async function appendExecutionLog(workspaceRoot, status, source, summary, options = {}) {
48
+ const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot);
49
+ const entry = buildExecutionLogEntry(status, source, summary, options);
50
+ const exists = await (0, workspace_1.fileExists)(paths.executionLog);
51
+ if (!exists) {
52
+ await (0, workspace_1.writeText)(paths.executionLog, buildExecutionLogHeader());
53
+ }
54
+ await (0, promises_1.appendFile)(paths.executionLog, entry, "utf8");
55
+ }
56
+ function normalizeSummary(summary) {
57
+ const compact = summary.replace(/\s+/g, " ").trim();
58
+ if (compact.length <= 180) {
59
+ return compact;
60
+ }
61
+ return `${compact.slice(0, 177)}...`;
62
+ }
63
+ function buildExecutionLogHeader() {
64
+ return [
65
+ "# Execution Log",
66
+ "",
67
+ "Durable execution history for `.srgical/` runs. Each entry records when a run happened, its final status, and a",
68
+ "concise summary for review and debugging.",
69
+ ""
70
+ ].join("\n");
71
+ }
72
+ function buildExecutionLogEntry(status, source, summary, options) {
73
+ const timestamp = new Date().toISOString();
74
+ const lines = [`## ${timestamp} - ${source} - ${status}`, ""];
75
+ if (options.stepLabel) {
76
+ lines.push(`- Step: ${options.stepLabel}`);
77
+ }
78
+ lines.push(`- Summary: ${normalizeLogSummary(summary)}`, "");
79
+ return `${lines.join("\n")}\n`;
80
+ }
81
+ function normalizeLogSummary(summary) {
82
+ const compact = summary.replace(/\s+/g, " ").trim();
83
+ if (compact.length <= 400) {
84
+ return compact;
85
+ }
86
+ return `${compact.slice(0, 397)}...`;
87
+ }
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writePlanningPackFallback = writePlanningPackFallback;
4
+ const templates_1 = require("./templates");
5
+ const workspace_1 = require("./workspace");
6
+ async function writePlanningPackFallback(workspaceRoot, messages, reason, agentLabel = "Codex") {
7
+ const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot);
8
+ const templates = (0, templates_1.getInitialTemplates)(paths);
9
+ const createdFiles = [];
10
+ const preservedFiles = [];
11
+ for (const [filePath, content] of Object.entries(templates)) {
12
+ const exists = await (0, workspace_1.fileExists)(filePath);
13
+ if (exists) {
14
+ preservedFiles.push(toPackLabel(paths, filePath));
15
+ continue;
16
+ }
17
+ await (0, workspace_1.writeText)(filePath, content);
18
+ createdFiles.push(toPackLabel(paths, filePath));
19
+ }
20
+ const currentPosition = await readCurrentPosition(paths.tracker);
21
+ const context = await (0, workspace_1.readText)(paths.context);
22
+ const updatedContext = appendFallbackEntry(context, buildFallbackEntry(messages, reason, createdFiles, preservedFiles, currentPosition, agentLabel));
23
+ await (0, workspace_1.writeText)(paths.context, updatedContext);
24
+ const summary = [
25
+ `Local fallback pack refresh completed because ${agentLabel} was unavailable.`,
26
+ `Reason: ${reason}`,
27
+ createdFiles.length > 0 ? `Created: ${createdFiles.join(", ")}` : "Created: none",
28
+ preservedFiles.length > 0 ? `Preserved: ${preservedFiles.join(", ")}` : "Preserved: none",
29
+ currentPosition.nextRecommended
30
+ ? `Tracker remains on next recommended step: ${currentPosition.nextRecommended}`
31
+ : "Tracker next step is not yet available."
32
+ ];
33
+ return summary.join("\n");
34
+ }
35
+ function buildFallbackEntry(messages, reason, createdFiles, preservedFiles, currentPosition, agentLabel) {
36
+ const now = new Date();
37
+ const transcriptSummary = summarizeRecentUserMessages(messages);
38
+ const nextRecommended = currentPosition.nextRecommended ?? "PLAN-001";
39
+ return [
40
+ `### ${now.toISOString().slice(0, 10)} - PACK-LOCAL - srgical`,
41
+ "",
42
+ `- Triggered an explicit local planning-pack refresh because ${agentLabel} was unavailable.`,
43
+ `- Reason: ${reason}.`,
44
+ createdFiles.length > 0
45
+ ? `- Created missing planning-pack files: ${createdFiles.join(", ")}.`
46
+ : "- Created missing planning-pack files: none.",
47
+ preservedFiles.length > 0
48
+ ? `- Preserved existing planning-pack files: ${preservedFiles.join(", ")}.`
49
+ : "- Preserved existing planning-pack files: none.",
50
+ transcriptSummary
51
+ ? `- Recent user direction: ${transcriptSummary}.`
52
+ : "- Recent user direction: no user transcript was available for summarization.",
53
+ `- Validation: local fallback pack refresh completed without invoking ${agentLabel}.`,
54
+ `- Blockers: live planner and live pack-authoring behavior remain unavailable until ${agentLabel} is restored.`,
55
+ `- Next recommended work: \`${nextRecommended}\`.`
56
+ ].join("\n");
57
+ }
58
+ async function readCurrentPosition(trackerPath) {
59
+ const exists = await (0, workspace_1.fileExists)(trackerPath);
60
+ if (!exists) {
61
+ return {
62
+ lastCompleted: null,
63
+ nextRecommended: null
64
+ };
65
+ }
66
+ try {
67
+ const tracker = await (0, workspace_1.readText)(trackerPath);
68
+ return {
69
+ lastCompleted: readCurrentPositionValue(tracker, "Last Completed"),
70
+ nextRecommended: readCurrentPositionValue(tracker, "Next Recommended")
71
+ };
72
+ }
73
+ catch {
74
+ return {
75
+ lastCompleted: null,
76
+ nextRecommended: null
77
+ };
78
+ }
79
+ }
80
+ function readCurrentPositionValue(tracker, label) {
81
+ const match = tracker.match(new RegExp(`- ${escapeRegExp(label)}: \`([^\`]+)\``));
82
+ return match?.[1] ?? null;
83
+ }
84
+ function summarizeRecentUserMessages(messages) {
85
+ const recent = messages
86
+ .filter((message) => message.role === "user")
87
+ .slice(-2)
88
+ .map((message) => sanitizeInlineText(message.content))
89
+ .filter(Boolean);
90
+ if (recent.length === 0) {
91
+ return "";
92
+ }
93
+ return recent.join(" | ");
94
+ }
95
+ function sanitizeInlineText(value) {
96
+ const collapsed = value.replace(/\s+/g, " ").trim();
97
+ if (collapsed.length <= 180) {
98
+ return collapsed;
99
+ }
100
+ return `${collapsed.slice(0, 177).trimEnd()}...`;
101
+ }
102
+ function appendFallbackEntry(context, entry) {
103
+ if (context.includes("## Handoff Log")) {
104
+ return `${context.trimEnd()}\n\n${entry}\n`;
105
+ }
106
+ return `${context.trimEnd()}\n\n## Handoff Log\n\n${entry}\n`;
107
+ }
108
+ function toPackLabel(paths, filePath) {
109
+ if (filePath === paths.plan) {
110
+ return ".srgical/01-product-plan.md";
111
+ }
112
+ if (filePath === paths.context) {
113
+ return ".srgical/02-agent-context-kickoff.md";
114
+ }
115
+ if (filePath === paths.tracker) {
116
+ return ".srgical/03-detailed-implementation-plan.md";
117
+ }
118
+ if (filePath === paths.nextPrompt) {
119
+ return ".srgical/04-next-agent-prompt.md";
120
+ }
121
+ return filePath;
122
+ }
123
+ function escapeRegExp(value) {
124
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
125
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.preparePlanningPackForWrite = preparePlanningPackForWrite;
7
+ exports.formatPlanningEpochSummary = formatPlanningEpochSummary;
8
+ exports.isArchivedPlanningDirName = isArchivedPlanningDirName;
9
+ exports.listArchivedPackFileNames = listArchivedPackFileNames;
10
+ const promises_1 = require("node:fs/promises");
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const planning_pack_state_1 = require("./planning-pack-state");
13
+ const templates_1 = require("./templates");
14
+ const workspace_1 = require("./workspace");
15
+ const ARCHIVED_PACK_FILE_NAMES = [
16
+ "01-product-plan.md",
17
+ "02-agent-context-kickoff.md",
18
+ "03-detailed-implementation-plan.md",
19
+ "04-next-agent-prompt.md",
20
+ "studio-session.json",
21
+ "execution-state.json",
22
+ "execution-log.md"
23
+ ];
24
+ async function preparePlanningPackForWrite(workspaceRoot) {
25
+ const packState = await (0, planning_pack_state_1.readPlanningPackState)(workspaceRoot);
26
+ if (!packState.packPresent || packState.currentPosition.nextRecommended) {
27
+ return {
28
+ archived: false,
29
+ archiveDir: null,
30
+ archivedFiles: []
31
+ };
32
+ }
33
+ const paths = await (0, workspace_1.ensurePlanningDir)(workspaceRoot);
34
+ const archiveDirName = await nextPlanningEpochName(paths.dir);
35
+ const archiveDir = node_path_1.default.join(paths.dir, archiveDirName);
36
+ await (0, promises_1.mkdir)(archiveDir, { recursive: false });
37
+ const archivedFiles = await archiveActivePlanningFiles(paths, archiveDir);
38
+ await resetActivePlanningPack(paths);
39
+ return {
40
+ archived: archivedFiles.length > 0,
41
+ archiveDir: archivedFiles.length > 0 ? toRelativePackPath(workspaceRoot, archiveDir) : null,
42
+ archivedFiles
43
+ };
44
+ }
45
+ function formatPlanningEpochSummary(preparation) {
46
+ if (!preparation.archived || !preparation.archiveDir) {
47
+ return null;
48
+ }
49
+ return [
50
+ `Started a new planning epoch by archiving the previous active pack to ${preparation.archiveDir}.`,
51
+ preparation.archivedFiles.length > 0
52
+ ? `Archived files: ${preparation.archivedFiles.join(", ")}`
53
+ : "Archived files: none"
54
+ ].join("\n");
55
+ }
56
+ async function archiveActivePlanningFiles(paths, archiveDir) {
57
+ const archivedFiles = [];
58
+ for (const sourcePath of listArchivablePaths(paths)) {
59
+ if (!(await (0, workspace_1.fileExists)(sourcePath))) {
60
+ continue;
61
+ }
62
+ const content = await (0, workspace_1.readText)(sourcePath);
63
+ const fileName = node_path_1.default.basename(sourcePath);
64
+ await (0, workspace_1.writeText)(node_path_1.default.join(archiveDir, fileName), content);
65
+ archivedFiles.push(fileName);
66
+ }
67
+ return archivedFiles;
68
+ }
69
+ async function resetActivePlanningPack(paths) {
70
+ const templates = (0, templates_1.getInitialTemplates)(paths);
71
+ await Promise.all(Object.entries(templates).map(([filePath, content]) => (0, workspace_1.writeText)(filePath, content)));
72
+ await Promise.all([
73
+ (0, promises_1.rm)(paths.executionState, { force: true }),
74
+ (0, promises_1.rm)(paths.executionLog, { force: true })
75
+ ]);
76
+ }
77
+ function listArchivablePaths(paths) {
78
+ return [
79
+ paths.plan,
80
+ paths.context,
81
+ paths.tracker,
82
+ paths.nextPrompt,
83
+ paths.studioSession,
84
+ paths.executionState,
85
+ paths.executionLog
86
+ ];
87
+ }
88
+ async function nextPlanningEpochName(planningDir) {
89
+ let highest = 0;
90
+ try {
91
+ const entries = await (0, promises_1.readdir)(planningDir, { withFileTypes: true });
92
+ for (const entry of entries) {
93
+ if (!entry.isDirectory()) {
94
+ continue;
95
+ }
96
+ const match = /^planning-(\d+)$/.exec(entry.name);
97
+ if (!match) {
98
+ continue;
99
+ }
100
+ highest = Math.max(highest, Number(match[1]));
101
+ }
102
+ }
103
+ catch {
104
+ return "planning-1";
105
+ }
106
+ return `planning-${highest + 1}`;
107
+ }
108
+ function toRelativePackPath(workspaceRoot, value) {
109
+ return node_path_1.default.relative(workspaceRoot, value).replace(/\\/g, "/");
110
+ }
111
+ function isArchivedPlanningDirName(value) {
112
+ return /^planning-\d+$/.test(value);
113
+ }
114
+ function listArchivedPackFileNames() {
115
+ return [...ARCHIVED_PACK_FILE_NAMES];
116
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readPlanningPackState = readPlanningPackState;
4
+ const execution_state_1 = require("./execution-state");
5
+ const workspace_1 = require("./workspace");
6
+ const workspace_2 = require("./workspace");
7
+ async function readPlanningPackState(workspaceRoot) {
8
+ const packPresent = await (0, workspace_1.planningPackExists)(workspaceRoot);
9
+ const currentPosition = emptyCurrentPosition();
10
+ let nextStepSummary = null;
11
+ let trackerReadable = false;
12
+ if (packPresent) {
13
+ try {
14
+ const tracker = await (0, workspace_1.readText)((0, workspace_2.getPlanningPackPaths)(workspaceRoot).tracker);
15
+ Object.assign(currentPosition, parseCurrentPosition(tracker));
16
+ nextStepSummary = parseNextStepSummary(tracker, currentPosition.nextRecommended);
17
+ trackerReadable = currentPosition.lastCompleted !== null || currentPosition.nextRecommended !== null;
18
+ }
19
+ catch {
20
+ trackerReadable = false;
21
+ }
22
+ }
23
+ const lastExecution = await (0, execution_state_1.loadExecutionState)(workspaceRoot);
24
+ return {
25
+ packPresent,
26
+ trackerReadable,
27
+ currentPosition,
28
+ nextStepSummary,
29
+ lastExecution
30
+ };
31
+ }
32
+ function parseCurrentPosition(tracker) {
33
+ return {
34
+ lastCompleted: readCurrentPositionValue(tracker, "Last Completed"),
35
+ nextRecommended: readCurrentPositionValue(tracker, "Next Recommended"),
36
+ updatedAt: readCurrentPositionValue(tracker, "Updated At")
37
+ };
38
+ }
39
+ function readCurrentPositionValue(tracker, label) {
40
+ const match = tracker.match(new RegExp(`- ${escapeRegExp(label)}: \`([^\`]+)\``));
41
+ return match?.[1] ?? null;
42
+ }
43
+ function escapeRegExp(value) {
44
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45
+ }
46
+ function parseNextStepSummary(tracker, stepId) {
47
+ if (!stepId) {
48
+ return null;
49
+ }
50
+ const rows = parseTrackerRows(tracker);
51
+ return rows.find((row) => row.id === stepId) ?? null;
52
+ }
53
+ function parseTrackerRows(tracker) {
54
+ const lines = tracker.split(/\r?\n/);
55
+ const rows = [];
56
+ let currentPhase = null;
57
+ for (let index = 0; index < lines.length; index += 1) {
58
+ const line = lines[index].trim();
59
+ if (line.startsWith("## ")) {
60
+ currentPhase = line.slice(3).trim();
61
+ continue;
62
+ }
63
+ if (!isTableRow(line) || index + 1 >= lines.length || !isTableSeparator(lines[index + 1].trim())) {
64
+ continue;
65
+ }
66
+ const headers = splitTableCells(line);
67
+ index += 2;
68
+ while (index < lines.length && isTableRow(lines[index].trim())) {
69
+ const cells = splitTableCells(lines[index].trim());
70
+ const row = buildTrackerRow(headers, cells, currentPhase);
71
+ if (row) {
72
+ rows.push(row);
73
+ }
74
+ index += 1;
75
+ }
76
+ index -= 1;
77
+ }
78
+ return rows;
79
+ }
80
+ function buildTrackerRow(headers, cells, phase) {
81
+ const values = new Map();
82
+ headers.forEach((header, index) => {
83
+ values.set(normalizeHeader(header), cells[index] ?? "");
84
+ });
85
+ const id = values.get("id") ?? "";
86
+ if (!id) {
87
+ return null;
88
+ }
89
+ return {
90
+ id,
91
+ status: values.get("status") ?? "",
92
+ dependsOn: values.get("depends_on") ?? "",
93
+ scope: values.get("scope") ?? "",
94
+ acceptance: values.get("acceptance") ?? "",
95
+ notes: values.get("notes") ?? "",
96
+ phase
97
+ };
98
+ }
99
+ function isTableRow(line) {
100
+ return line.startsWith("|") && line.endsWith("|");
101
+ }
102
+ function isTableSeparator(line) {
103
+ return /^\|(?:\s*:?-+:?\s*\|)+$/.test(line);
104
+ }
105
+ function splitTableCells(line) {
106
+ const trimmed = line.slice(1, -1);
107
+ return trimmed.split("|").map((cell) => cell.trim());
108
+ }
109
+ function normalizeHeader(value) {
110
+ return value
111
+ .toLowerCase()
112
+ .replace(/[^a-z0-9]+/g, "_")
113
+ .replace(/^_+|_+$/g, "");
114
+ }
115
+ function emptyCurrentPosition() {
116
+ return {
117
+ lastCompleted: null,
118
+ nextRecommended: null,
119
+ updatedAt: null
120
+ };
121
+ }
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildPlannerPrompt = buildPlannerPrompt;
7
+ exports.buildPackWriterPrompt = buildPackWriterPrompt;
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const workspace_1 = require("./workspace");
11
+ const REPO_FILE_LIST_LIMIT = 24;
12
+ const FILE_SNIPPET_LIMIT = 2200;
13
+ function renderTranscript(messages) {
14
+ return messages
15
+ .map((message, index) => {
16
+ const label = message.role === "user" ? "User" : message.role === "assistant" ? "Planner" : "System";
17
+ return `${index + 1}. ${label}: ${message.content}`;
18
+ })
19
+ .join("\n\n");
20
+ }
21
+ function buildPlannerPrompt(messages, workspaceRoot) {
22
+ return `You are the planning partner inside srgical, a local-first CLI that helps users turn long AI-driven delivery
23
+ projects into a disciplined execution pack.
24
+
25
+ Your job in this mode is conversation only. Do not write files. Do not output markdown boilerplate. Help the user
26
+ clarify the project and get it ready for pack generation.
27
+
28
+ Rules:
29
+ - Be concise, sharp, and practical.
30
+ - Ask at most one clarifying question at a time.
31
+ - Prefer decisions over brainstorming sprawl.
32
+ - Optimize for shipping a concrete first version.
33
+ - The current workspace is: ${workspaceRoot}
34
+
35
+ Conversation so far:
36
+
37
+ ${renderTranscript(messages)}
38
+
39
+ Respond as the planning partner.`;
40
+ }
41
+ async function buildPackWriterPrompt(messages, workspaceRoot) {
42
+ const repoTruth = await buildRepoTruthSnapshot(workspaceRoot);
43
+ return `You are writing a planning pack for the current repository.
44
+
45
+ Read the conversation transcript below and update or create the following files under .srgical/:
46
+
47
+ - 01-product-plan.md
48
+ - 02-agent-context-kickoff.md
49
+ - 03-detailed-implementation-plan.md
50
+ - 04-next-agent-prompt.md
51
+
52
+ Operating rules:
53
+ - Start from the repo truth snapshot below, not from generic assumptions.
54
+ - Treat any existing .srgical files as current project state to refine in place, not blank templates to overwrite casually.
55
+ - Preserve valid locked decisions, completed steps, and step IDs unless the repo truth or conversation clearly requires a change.
56
+ - Prefer repo truth for what already exists in the codebase and the conversation for what the user now wants next.
57
+ - Make the pack specific to the actual commands, docs, stack, and current capabilities in this workspace.
58
+ - Do not invent frameworks, release channels, adapters, tests, or subsystems that are not supported by the repo truth or transcript.
59
+ - Keep the workflow local-first, explicit, incremental, and validation-aware.
60
+
61
+ Quality bar:
62
+ - 01-product-plan.md should capture the real product direction, locked decisions, and current repo findings.
63
+ - 02-agent-context-kickoff.md should capture current repo truth, working agreements, current position, and a concise handoff log.
64
+ - 03-detailed-implementation-plan.md should keep a readable status legend, current position, phase-based next steps, and concrete notes.
65
+ - 04-next-agent-prompt.md should enforce incremental execution, validation, tracker updates, and stop conditions.
66
+ - The tracker should stay execution-ready: use concrete step IDs, realistic acceptance criteria, and concise validation notes instead of filler.
67
+
68
+ Repo truth snapshot:
69
+
70
+ ${repoTruth}
71
+
72
+ Conversation transcript:
73
+
74
+ ${renderTranscript(messages)}
75
+ `;
76
+ }
77
+ async function buildRepoTruthSnapshot(workspaceRoot) {
78
+ const paths = (0, workspace_1.getPlanningPackPaths)(workspaceRoot);
79
+ const [packageSummary, topLevelEntries, sourceFiles, docFiles, readmeSnippet, foundationSnippet, adrSnippet, planSnippet, contextSnippet, trackerSnippet, nextPromptSnippet] = await Promise.all([
80
+ summarizePackageManifest(workspaceRoot),
81
+ listDirectoryEntries(workspaceRoot),
82
+ listRelativeFiles(node_path_1.default.join(workspaceRoot, "src"), workspaceRoot, REPO_FILE_LIST_LIMIT),
83
+ listRelativeFiles(node_path_1.default.join(workspaceRoot, "docs"), workspaceRoot, REPO_FILE_LIST_LIMIT),
84
+ readSnippet(workspaceRoot, "README.md", FILE_SNIPPET_LIMIT),
85
+ readSnippet(workspaceRoot, "docs/product-foundation.md", FILE_SNIPPET_LIMIT),
86
+ readSnippet(workspaceRoot, "docs/adr/0001-tech-stack.md", FILE_SNIPPET_LIMIT),
87
+ readOptionalAbsoluteSnippet(paths.plan, workspaceRoot, FILE_SNIPPET_LIMIT),
88
+ readOptionalAbsoluteSnippet(paths.context, workspaceRoot, FILE_SNIPPET_LIMIT),
89
+ readOptionalAbsoluteSnippet(paths.tracker, workspaceRoot, FILE_SNIPPET_LIMIT),
90
+ readOptionalAbsoluteSnippet(paths.nextPrompt, workspaceRoot, FILE_SNIPPET_LIMIT)
91
+ ]);
92
+ return [
93
+ `Workspace root: ${workspaceRoot}`,
94
+ "",
95
+ "Top-level repo entries:",
96
+ renderList(topLevelEntries),
97
+ "",
98
+ "Source file inventory:",
99
+ renderList(sourceFiles),
100
+ "",
101
+ "Docs file inventory:",
102
+ renderList(docFiles),
103
+ "",
104
+ "Package manifest:",
105
+ packageSummary,
106
+ "",
107
+ "Key docs:",
108
+ renderNamedSnippet("README.md", readmeSnippet),
109
+ "",
110
+ renderNamedSnippet("docs/product-foundation.md", foundationSnippet),
111
+ "",
112
+ renderNamedSnippet("docs/adr/0001-tech-stack.md", adrSnippet),
113
+ "",
114
+ "Existing planning-pack files:",
115
+ renderNamedSnippet(".srgical/01-product-plan.md", planSnippet),
116
+ "",
117
+ renderNamedSnippet(".srgical/02-agent-context-kickoff.md", contextSnippet),
118
+ "",
119
+ renderNamedSnippet(".srgical/03-detailed-implementation-plan.md", trackerSnippet),
120
+ "",
121
+ renderNamedSnippet(".srgical/04-next-agent-prompt.md", nextPromptSnippet)
122
+ ].join("\n");
123
+ }
124
+ async function summarizePackageManifest(workspaceRoot) {
125
+ const packageJsonPath = node_path_1.default.join(workspaceRoot, "package.json");
126
+ const exists = await (0, workspace_1.fileExists)(packageJsonPath);
127
+ if (!exists) {
128
+ return "- package.json not present";
129
+ }
130
+ try {
131
+ const raw = await (0, workspace_1.readText)(packageJsonPath);
132
+ const parsed = JSON.parse(raw);
133
+ const scripts = Object.entries(parsed.scripts ?? {}).map(([name, command]) => `${name}: ${command}`);
134
+ const dependencies = Object.entries(parsed.dependencies ?? {}).map(([name, version]) => `${name}: ${version}`);
135
+ const devDependencies = Object.entries(parsed.devDependencies ?? {}).map(([name, version]) => `${name}: ${version}`);
136
+ return [
137
+ `- name: ${parsed.name ?? "unknown"}`,
138
+ `- version: ${parsed.version ?? "unknown"}`,
139
+ `- description: ${parsed.description ?? "none"}`,
140
+ "- scripts:",
141
+ renderList(scripts),
142
+ "- dependencies:",
143
+ renderList(dependencies),
144
+ "- devDependencies:",
145
+ renderList(devDependencies)
146
+ ].join("\n");
147
+ }
148
+ catch {
149
+ const fallback = await (0, workspace_1.readText)(packageJsonPath);
150
+ return limitText(fallback.trim(), FILE_SNIPPET_LIMIT);
151
+ }
152
+ }
153
+ async function listDirectoryEntries(directoryPath) {
154
+ try {
155
+ const entries = await (0, promises_1.readdir)(directoryPath, { withFileTypes: true });
156
+ return entries
157
+ .filter((entry) => entry.name !== ".git" && entry.name !== "node_modules")
158
+ .map((entry) => (entry.isDirectory() ? `${entry.name}/` : entry.name))
159
+ .sort((left, right) => left.localeCompare(right));
160
+ }
161
+ catch {
162
+ return ["- unavailable"];
163
+ }
164
+ }
165
+ async function listRelativeFiles(directoryPath, workspaceRoot, maxCount) {
166
+ const collected = [];
167
+ const exists = await (0, workspace_1.fileExists)(directoryPath);
168
+ if (!exists) {
169
+ return ["- unavailable"];
170
+ }
171
+ await walkFiles(directoryPath, workspaceRoot, collected, maxCount);
172
+ if (collected.length === 0) {
173
+ return ["- none"];
174
+ }
175
+ return collected;
176
+ }
177
+ async function walkFiles(directoryPath, workspaceRoot, collected, maxCount) {
178
+ if (collected.length >= maxCount || hasTruncationMarker(collected)) {
179
+ return;
180
+ }
181
+ const entries = await (0, promises_1.readdir)(directoryPath, { withFileTypes: true });
182
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
183
+ if (collected.length >= maxCount) {
184
+ collected.push(`... truncated after ${maxCount} files`);
185
+ return;
186
+ }
187
+ const fullPath = node_path_1.default.join(directoryPath, entry.name);
188
+ if (entry.isDirectory()) {
189
+ await walkFiles(fullPath, workspaceRoot, collected, maxCount);
190
+ continue;
191
+ }
192
+ collected.push(node_path_1.default.relative(workspaceRoot, fullPath).replace(/\\/g, "/"));
193
+ }
194
+ }
195
+ async function readSnippet(workspaceRoot, relativePath, maxChars) {
196
+ return readOptionalAbsoluteSnippet(node_path_1.default.join(workspaceRoot, relativePath), workspaceRoot, maxChars);
197
+ }
198
+ async function readOptionalAbsoluteSnippet(filePath, workspaceRoot, maxChars) {
199
+ const exists = await (0, workspace_1.fileExists)(filePath);
200
+ if (!exists) {
201
+ return "- missing";
202
+ }
203
+ try {
204
+ const content = await (0, workspace_1.readText)(filePath);
205
+ const relativePath = node_path_1.default.relative(workspaceRoot, filePath).replace(/\\/g, "/");
206
+ return [`Path: ${relativePath}`, limitText(content.trim(), maxChars)].join("\n");
207
+ }
208
+ catch {
209
+ return "- unreadable";
210
+ }
211
+ }
212
+ function renderNamedSnippet(name, content) {
213
+ return [`${name}:`, content].join("\n");
214
+ }
215
+ function renderList(items) {
216
+ if (items.length === 0) {
217
+ return "- none";
218
+ }
219
+ return items.map((item) => (item.startsWith("- ") ? item : `- ${item}`)).join("\n");
220
+ }
221
+ function limitText(value, maxChars) {
222
+ if (value.length <= maxChars) {
223
+ return value;
224
+ }
225
+ // Keep snippets short enough to ground the planner without overwhelming the prompt.
226
+ return `${value.slice(0, maxChars).trimEnd()}\n... [truncated after ${maxChars} chars]`;
227
+ }
228
+ function hasTruncationMarker(items) {
229
+ return items.some((item) => item.startsWith("... truncated after "));
230
+ }