@jskit-ai/jskit-cli 0.2.87 → 0.2.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.89",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.88",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.80",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.79",
|
|
26
26
|
"@vue/compiler-sfc": "^3.5.29",
|
|
27
27
|
"ts-morph": "^28.0.0"
|
|
28
28
|
},
|
|
@@ -147,6 +147,10 @@ const JSKIT_STEP_RESULT_CONTRACT = Object.freeze({
|
|
|
147
147
|
required: true,
|
|
148
148
|
stepField: "step"
|
|
149
149
|
});
|
|
150
|
+
const MANUAL_JSKIT_STEP_RESULT_CONTRACT = Object.freeze({
|
|
151
|
+
...JSKIT_STEP_RESULT_CONTRACT,
|
|
152
|
+
completionBehavior: "manual_advance"
|
|
153
|
+
});
|
|
150
154
|
const DESLOP_RESULT_CONTRACT = Object.freeze({
|
|
151
155
|
autoResolvePriorities: Object.freeze(["high", "medium"]),
|
|
152
156
|
completionBehavior: "deslop_loop",
|
|
@@ -231,7 +235,7 @@ function stepAutomationFor({
|
|
|
231
235
|
const PLAN_EXECUTION_CODEX_HANDOFF = codexHandoff([], {
|
|
232
236
|
autoInject: true,
|
|
233
237
|
promptActionLabel: "Get Codex to execute plan",
|
|
234
|
-
responseContract:
|
|
238
|
+
responseContract: MANUAL_JSKIT_STEP_RESULT_CONTRACT
|
|
235
239
|
});
|
|
236
240
|
const ISSUE_DETAILS_CODEX_HANDOFF = codexHandoff([
|
|
237
241
|
ISSUE_CATEGORY_OUTPUT,
|
|
@@ -407,7 +411,7 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
407
411
|
defineStep({
|
|
408
412
|
buttonLabel: "Get Codex to execute plan",
|
|
409
413
|
codex: PLAN_EXECUTION_CODEX_HANDOFF,
|
|
410
|
-
description: "JSKIT sends the active cycle plan to Codex; Codex implements it;
|
|
414
|
+
description: "JSKIT sends the active cycle plan to Codex; Codex implements it; the user advances after reviewing completion.",
|
|
411
415
|
id: "plan_executed",
|
|
412
416
|
kind: "codex_prompt",
|
|
413
417
|
label: "Plan executed",
|
|
@@ -473,28 +477,28 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
473
477
|
})
|
|
474
478
|
])
|
|
475
479
|
}),
|
|
476
|
-
defineStep({
|
|
477
|
-
buttonLabel: "Commit accepted changes",
|
|
478
|
-
description: "JSKIT commits the user-accepted session changes in the session worktree.",
|
|
479
|
-
id: "changes_committed",
|
|
480
|
-
label: "Changes committed",
|
|
481
|
-
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "issue_url_exists", "github_auth", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed"]
|
|
482
|
-
}),
|
|
483
480
|
defineStep({
|
|
484
481
|
buttonLabel: "Update blueprint",
|
|
485
482
|
codex: BLUEPRINT_CODEX_HANDOFF,
|
|
486
|
-
description: "JSKIT asks Codex to update durable app memory from the accepted work; Codex edits .jskit/APP_BLUEPRINT.md; JSKIT records
|
|
483
|
+
description: "JSKIT asks Codex to update durable app memory from the accepted work; Codex edits .jskit/APP_BLUEPRINT.md; JSKIT records the update for the accepted-work commit.",
|
|
487
484
|
id: "blueprint_updated",
|
|
488
485
|
kind: "codex_prompt",
|
|
489
486
|
label: "Blueprint updated",
|
|
490
|
-
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed"
|
|
487
|
+
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed"]
|
|
488
|
+
}),
|
|
489
|
+
defineStep({
|
|
490
|
+
buttonLabel: "Commit accepted changes",
|
|
491
|
+
description: "JSKIT commits the accepted session changes, including durable app memory updates, in the session worktree.",
|
|
492
|
+
id: "changes_committed",
|
|
493
|
+
label: "Changes committed",
|
|
494
|
+
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "issue_url_exists", "github_auth", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed", "blueprint_update_satisfied"]
|
|
491
495
|
}),
|
|
492
496
|
defineStep({
|
|
493
497
|
buttonLabel: "Create final report",
|
|
494
498
|
description: "JSKIT creates the deterministic final session report and comments it on the GitHub issue.",
|
|
495
499
|
id: "final_report_created",
|
|
496
500
|
label: "Final report created",
|
|
497
|
-
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed", "
|
|
501
|
+
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed", "blueprint_update_satisfied", "accepted_changes_committed"]
|
|
498
502
|
}),
|
|
499
503
|
defineStep({
|
|
500
504
|
buttonLabel: "Push branch and create PR",
|
|
@@ -379,7 +379,7 @@ async function assertAcceptedChangesCommitted(paths) {
|
|
|
379
379
|
ok: false,
|
|
380
380
|
error: createError({
|
|
381
381
|
code: "accepted_changes_not_committed",
|
|
382
|
-
message: "Accepted changes must be committed before
|
|
382
|
+
message: "Accepted changes must be committed before finalization steps continue.",
|
|
383
383
|
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
384
384
|
}),
|
|
385
385
|
precondition: createPrecondition({
|
|
@@ -56,6 +56,19 @@ function createPrecondition({
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function createWarning({
|
|
60
|
+
code,
|
|
61
|
+
message,
|
|
62
|
+
repairCommand = ""
|
|
63
|
+
}) {
|
|
64
|
+
return createError({ code, message, repairCommand });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ACCEPTED_CHANGES_NOOP_WARNING = createWarning({
|
|
68
|
+
code: "accepted_changes_noop",
|
|
69
|
+
message: "No accepted worktree changes were found; continuing without a new commit."
|
|
70
|
+
});
|
|
71
|
+
|
|
59
72
|
function normalizeStepId(stepId) {
|
|
60
73
|
return normalizeText(stepId);
|
|
61
74
|
}
|
|
@@ -530,6 +543,43 @@ function cloneContractValue(value) {
|
|
|
530
543
|
);
|
|
531
544
|
}
|
|
532
545
|
|
|
546
|
+
function normalizeWarning(warning) {
|
|
547
|
+
if (typeof warning === "string") {
|
|
548
|
+
return createWarning({
|
|
549
|
+
code: "session_warning",
|
|
550
|
+
message: warning
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (!warning || typeof warning !== "object" || Array.isArray(warning)) {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
return createWarning({
|
|
557
|
+
code: warning.code || "session_warning",
|
|
558
|
+
message: warning.message || "",
|
|
559
|
+
repairCommand: warning.repairCommand || ""
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function mergeWarnings(...warningLists) {
|
|
564
|
+
const merged = [];
|
|
565
|
+
const seen = new Set();
|
|
566
|
+
for (const warnings of warningLists) {
|
|
567
|
+
for (const warning of Array.isArray(warnings) ? warnings : []) {
|
|
568
|
+
const normalized = normalizeWarning(warning);
|
|
569
|
+
if (!normalized?.message) {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
const key = `${normalized.code}\n${normalized.message}`;
|
|
573
|
+
if (seen.has(key)) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
seen.add(key);
|
|
577
|
+
merged.push(normalized);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return merged;
|
|
581
|
+
}
|
|
582
|
+
|
|
533
583
|
async function publicCodexContract(codex = null) {
|
|
534
584
|
if (!codex || typeof codex !== "object" || Array.isArray(codex)) {
|
|
535
585
|
return null;
|
|
@@ -731,7 +781,7 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
731
781
|
})();
|
|
732
782
|
const dynamicDescription = (() => {
|
|
733
783
|
if (step.id === "plan_executed" && planExecutionPrompted && !planExecutionSubmitted) {
|
|
734
|
-
return "Codex has the execution prompt.
|
|
784
|
+
return "Codex has the execution prompt. Review the result, then use Go to next step when ready.";
|
|
735
785
|
}
|
|
736
786
|
if (step.id === "deep_ui_check_run" && deepUiCheckPrompted) {
|
|
737
787
|
return "Codex has the Deep UI check prompt. Studio advances when Codex finishes.";
|
|
@@ -814,7 +864,8 @@ async function readSessionArtifacts(paths) {
|
|
|
814
864
|
issueMetadataText,
|
|
815
865
|
planExecutionReceipt,
|
|
816
866
|
prOutcomeText,
|
|
817
|
-
mainCheckoutSyncText
|
|
867
|
+
mainCheckoutSyncText,
|
|
868
|
+
changesCommittedText
|
|
818
869
|
] = await Promise.all([
|
|
819
870
|
readTrimmedFile(path.join(paths.sessionRoot, "status")),
|
|
820
871
|
readTrimmedFile(path.join(paths.sessionRoot, "current_step")),
|
|
@@ -834,7 +885,8 @@ async function readSessionArtifacts(paths) {
|
|
|
834
885
|
readTextIfExists(path.join(paths.sessionRoot, "issue_metadata.json")),
|
|
835
886
|
readTextIfExists(path.join(cycleStepsRoot(paths, activeCycle), "plan_executed")),
|
|
836
887
|
readTextIfExists(path.join(paths.sessionRoot, "pr_outcome.json")),
|
|
837
|
-
readTextIfExists(path.join(paths.sessionRoot, "main_checkout_sync.json"))
|
|
888
|
+
readTextIfExists(path.join(paths.sessionRoot, "main_checkout_sync.json")),
|
|
889
|
+
readTextIfExists(path.join(paths.sessionRoot, "changes_committed.json"))
|
|
838
890
|
]);
|
|
839
891
|
let issueMetadata = null;
|
|
840
892
|
if (issueMetadataText) {
|
|
@@ -871,6 +923,18 @@ async function readSessionArtifacts(paths) {
|
|
|
871
923
|
mainCheckoutSync = null;
|
|
872
924
|
}
|
|
873
925
|
}
|
|
926
|
+
let acceptedChangesCommit = null;
|
|
927
|
+
if (changesCommittedText) {
|
|
928
|
+
try {
|
|
929
|
+
const parsed = JSON.parse(changesCommittedText);
|
|
930
|
+
acceptedChangesCommit = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
931
|
+
} catch {
|
|
932
|
+
acceptedChangesCommit = null;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
const warnings = acceptedChangesCommit?.noChanges === true
|
|
936
|
+
? [ACCEPTED_CHANGES_NOOP_WARNING]
|
|
937
|
+
: [];
|
|
874
938
|
const cycles = await readCycles(paths, activeCycle);
|
|
875
939
|
const checks = await readStructuredChecks(paths);
|
|
876
940
|
const uiChecks = await readStructuredUiChecks(paths);
|
|
@@ -960,6 +1024,7 @@ async function readSessionArtifacts(paths) {
|
|
|
960
1024
|
finalReportText: finalReportText.trim(),
|
|
961
1025
|
prompt: prompt.trim(),
|
|
962
1026
|
status: status || SESSION_STATUS.PENDING,
|
|
1027
|
+
warnings,
|
|
963
1028
|
workflowVersion,
|
|
964
1029
|
worktreeReady,
|
|
965
1030
|
worktreeStatus
|
|
@@ -980,7 +1045,8 @@ async function buildSessionResponse(paths, {
|
|
|
980
1045
|
errors = [],
|
|
981
1046
|
preconditions = [],
|
|
982
1047
|
prompt = undefined,
|
|
983
|
-
status = undefined
|
|
1048
|
+
status = undefined,
|
|
1049
|
+
warnings = []
|
|
984
1050
|
} = {}) {
|
|
985
1051
|
const responsePaths = paths.sessionId ? await pathsForExistingSession(paths) : paths;
|
|
986
1052
|
const artifacts = responsePaths.sessionRoot ? await readSessionArtifacts(responsePaths) : {};
|
|
@@ -1039,6 +1105,7 @@ async function buildSessionResponse(paths, {
|
|
|
1039
1105
|
message: `Session ${paths.sessionId || ""} uses workflow version ${artifacts.workflowVersion || "missing"}, but this JSKIT runtime expects ${SESSION_WORKFLOW_VERSION}.`
|
|
1040
1106
|
})
|
|
1041
1107
|
],
|
|
1108
|
+
warnings: [],
|
|
1042
1109
|
archive: responsePaths.archive || "active",
|
|
1043
1110
|
sessionRoot: responsePaths.sessionRoot || "",
|
|
1044
1111
|
worktree: paths.worktree || "",
|
|
@@ -1052,6 +1119,7 @@ async function buildSessionResponse(paths, {
|
|
|
1052
1119
|
const responsePrompt = typeof prompt === "string"
|
|
1053
1120
|
? prompt
|
|
1054
1121
|
: stepCanExposeStoredPrompt(currentStep) ? artifacts.prompt || "" : "";
|
|
1122
|
+
const responseWarnings = mergeWarnings(artifacts.warnings || [], warnings);
|
|
1055
1123
|
|
|
1056
1124
|
return {
|
|
1057
1125
|
ok: ok === true,
|
|
@@ -1101,6 +1169,7 @@ async function buildSessionResponse(paths, {
|
|
|
1101
1169
|
mainCheckoutSync: cloneContractValue(artifacts.mainCheckoutSync || null),
|
|
1102
1170
|
preconditions,
|
|
1103
1171
|
errors,
|
|
1172
|
+
warnings: responseWarnings,
|
|
1104
1173
|
archive: responsePaths.archive || (resolvedStatus === SESSION_STATUS.FINISHED ? "completed" : resolvedStatus === SESSION_STATUS.ABANDONED ? "abandoned" : "active"),
|
|
1105
1174
|
sessionRoot: responsePaths.sessionRoot || "",
|
|
1106
1175
|
worktree: paths.worktree || "",
|
|
@@ -1179,6 +1248,7 @@ function buildSessionErrorResponse({
|
|
|
1179
1248
|
prOutcome: null,
|
|
1180
1249
|
preconditions,
|
|
1181
1250
|
errors: errorList,
|
|
1251
|
+
warnings: [],
|
|
1182
1252
|
archive: "",
|
|
1183
1253
|
sessionRoot: "",
|
|
1184
1254
|
worktree: "",
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHash
|
|
3
|
+
} from "node:crypto";
|
|
1
4
|
import {
|
|
2
5
|
appendFile,
|
|
3
6
|
mkdir,
|
|
@@ -1581,6 +1584,7 @@ const STEP_CANCELERS = Object.freeze({
|
|
|
1581
1584
|
blueprint_updated: async (paths) => {
|
|
1582
1585
|
await Promise.all([
|
|
1583
1586
|
removePromptArtifact(paths, "update_blueprint.md"),
|
|
1587
|
+
removeSessionRootFile(paths, BLUEPRINT_BASELINE_FILE),
|
|
1584
1588
|
removeGlobalCodexResult(paths, "blueprint_updated")
|
|
1585
1589
|
]);
|
|
1586
1590
|
},
|
|
@@ -1830,6 +1834,79 @@ async function changedFilesInWorktree(paths) {
|
|
|
1830
1834
|
]);
|
|
1831
1835
|
}
|
|
1832
1836
|
|
|
1837
|
+
const BLUEPRINT_RELATIVE_PATH = ".jskit/APP_BLUEPRINT.md";
|
|
1838
|
+
const BLUEPRINT_BASELINE_FILE = "blueprint_update_baseline.json";
|
|
1839
|
+
|
|
1840
|
+
function blueprintBaselinePath(paths) {
|
|
1841
|
+
return path.join(paths.sessionRoot, BLUEPRINT_BASELINE_FILE);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
function isBlueprintRelativePath(filePath = "") {
|
|
1845
|
+
return normalizeText(filePath) === BLUEPRINT_RELATIVE_PATH;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function nonBlueprintChangedFiles(files = []) {
|
|
1849
|
+
return files.filter((file) => !isBlueprintRelativePath(file));
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
async function hashWorktreeFile(paths, filePath) {
|
|
1853
|
+
try {
|
|
1854
|
+
const buffer = await readFile(path.join(paths.worktree, filePath));
|
|
1855
|
+
return createHash("sha256").update(buffer).digest("hex");
|
|
1856
|
+
} catch {
|
|
1857
|
+
return "missing";
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
async function buildDirtyFileSnapshot(paths, files = []) {
|
|
1862
|
+
const entries = await Promise.all(nonBlueprintChangedFiles(files).map(async (file) => [
|
|
1863
|
+
file,
|
|
1864
|
+
await hashWorktreeFile(paths, file)
|
|
1865
|
+
]));
|
|
1866
|
+
return Object.fromEntries(entries);
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
async function writeBlueprintBaseline(paths) {
|
|
1870
|
+
const changedFiles = await changedFilesInWorktree(paths);
|
|
1871
|
+
const snapshot = await buildDirtyFileSnapshot(paths, changedFiles);
|
|
1872
|
+
const payload = {
|
|
1873
|
+
changedFiles: Object.keys(snapshot).sort((left, right) => left.localeCompare(right)),
|
|
1874
|
+
files: snapshot,
|
|
1875
|
+
recordedAt: timestampForReceipt()
|
|
1876
|
+
};
|
|
1877
|
+
await writeTextFile(blueprintBaselinePath(paths), `${JSON.stringify(payload, null, 2)}\n`);
|
|
1878
|
+
return payload;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
async function readBlueprintBaseline(paths) {
|
|
1882
|
+
return parseJsonObject(await readTextIfExists(blueprintBaselinePath(paths))) || null;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
async function unexpectedBlueprintStepChanges(paths, changedFiles = []) {
|
|
1886
|
+
const baseline = await readBlueprintBaseline(paths);
|
|
1887
|
+
if (!baseline?.files || typeof baseline.files !== "object" || Array.isArray(baseline.files)) {
|
|
1888
|
+
return nonBlueprintChangedFiles(changedFiles);
|
|
1889
|
+
}
|
|
1890
|
+
const baselineFiles = baseline.files;
|
|
1891
|
+
const currentFiles = new Set(nonBlueprintChangedFiles(changedFiles));
|
|
1892
|
+
const candidates = new Set([
|
|
1893
|
+
...Object.keys(baselineFiles),
|
|
1894
|
+
...currentFiles
|
|
1895
|
+
]);
|
|
1896
|
+
const unexpected = [];
|
|
1897
|
+
for (const file of [...candidates].sort((left, right) => left.localeCompare(right))) {
|
|
1898
|
+
if (!Object.prototype.hasOwnProperty.call(baselineFiles, file)) {
|
|
1899
|
+
unexpected.push(file);
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
const currentHash = await hashWorktreeFile(paths, file);
|
|
1903
|
+
if (currentHash !== baselineFiles[file]) {
|
|
1904
|
+
unexpected.push(file);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
return unexpected;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1833
1910
|
async function changedFilesSinceBase(paths) {
|
|
1834
1911
|
const baseCommit = await readTrimmedFile(path.join(paths.sessionRoot, "base_commit"));
|
|
1835
1912
|
const args = baseCommit
|
|
@@ -2178,17 +2255,10 @@ async function commitAcceptedChanges(paths, _options = {}, context = {}) {
|
|
|
2178
2255
|
|
|
2179
2256
|
if (!commitInfo?.commit) {
|
|
2180
2257
|
const result = await commitWorktree(paths, {
|
|
2258
|
+
allowNoChanges: true,
|
|
2181
2259
|
message: `Implement JSKIT session ${paths.sessionId}`
|
|
2182
2260
|
});
|
|
2183
2261
|
if (!result.ok) {
|
|
2184
|
-
if (result.output === "No changes found.") {
|
|
2185
|
-
return failSession(paths, {
|
|
2186
|
-
code: "accepted_changes_missing",
|
|
2187
|
-
message: "No accepted worktree changes found to commit.",
|
|
2188
|
-
repairCommand: `git -C ${paths.worktree} status --short`,
|
|
2189
|
-
preconditions
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
2262
|
return failSession(paths, {
|
|
2193
2263
|
code: "accepted_changes_commit_failed",
|
|
2194
2264
|
message: result.output || "Failed to commit accepted changes.",
|
|
@@ -2199,15 +2269,30 @@ async function commitAcceptedChanges(paths, _options = {}, context = {}) {
|
|
|
2199
2269
|
commitInfo = {
|
|
2200
2270
|
changedFiles: result.changedFiles || [],
|
|
2201
2271
|
commit: await currentHead(paths),
|
|
2202
|
-
committedAt: timestampForReceipt()
|
|
2272
|
+
committedAt: timestampForReceipt(),
|
|
2273
|
+
noChanges: (result.changedFiles || []).length < 1
|
|
2203
2274
|
};
|
|
2204
2275
|
await writeTextFile(path.join(paths.sessionRoot, "changes_committed.json"), `${JSON.stringify(commitInfo, null, 2)}\n`);
|
|
2205
2276
|
}
|
|
2206
2277
|
|
|
2207
|
-
|
|
2278
|
+
const warnings = [];
|
|
2279
|
+
if (commitInfo.noChanges === true) {
|
|
2280
|
+
warnings.push({
|
|
2281
|
+
code: "accepted_changes_noop",
|
|
2282
|
+
message: "No accepted worktree changes were found; continuing without a new commit."
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
await writeReceipt(
|
|
2286
|
+
paths,
|
|
2287
|
+
"changes_committed",
|
|
2288
|
+
commitInfo.noChanges === true
|
|
2289
|
+
? "No accepted worktree changes were found; continued without a new commit."
|
|
2290
|
+
: `Committed accepted changes at ${commitInfo.commit || "unknown"}.`
|
|
2291
|
+
);
|
|
2208
2292
|
await markStatus(paths, SESSION_STATUS.RUNNING);
|
|
2209
2293
|
return buildSessionResponse(paths, {
|
|
2210
|
-
preconditions
|
|
2294
|
+
preconditions,
|
|
2295
|
+
warnings
|
|
2211
2296
|
});
|
|
2212
2297
|
}
|
|
2213
2298
|
|
|
@@ -2220,7 +2305,7 @@ async function updateBlueprint(paths, options = {}, context = {}) {
|
|
|
2220
2305
|
const { planPath } = await readCurrentPlan(paths);
|
|
2221
2306
|
const issueDetailsPath = path.join(paths.sessionRoot, "issue_details.md");
|
|
2222
2307
|
const agentDecisionsPath = path.join(paths.sessionRoot, "agent_decisions.md");
|
|
2223
|
-
const blueprintPath = path.join(paths.worktree,
|
|
2308
|
+
const blueprintPath = path.join(paths.worktree, BLUEPRINT_RELATIVE_PATH);
|
|
2224
2309
|
const blueprintPromptPath = path.join(paths.sessionRoot, "prompts", "update_blueprint.md");
|
|
2225
2310
|
|
|
2226
2311
|
if (await fileExists(blueprintPromptPath)) {
|
|
@@ -2229,11 +2314,11 @@ async function updateBlueprint(paths, options = {}, context = {}) {
|
|
|
2229
2314
|
return codexResultFailure;
|
|
2230
2315
|
}
|
|
2231
2316
|
const changedFiles = await changedFilesInWorktree(paths);
|
|
2232
|
-
const unexpectedChanges =
|
|
2317
|
+
const unexpectedChanges = await unexpectedBlueprintStepChanges(paths, changedFiles);
|
|
2233
2318
|
if (unexpectedChanges.length > 0) {
|
|
2234
2319
|
return failSession(paths, {
|
|
2235
2320
|
code: "blueprint_unexpected_changes",
|
|
2236
|
-
message: `The blueprint step changed files outside
|
|
2321
|
+
message: `The blueprint step changed files outside ${BLUEPRINT_RELATIVE_PATH}: ${unexpectedChanges.join(", ")}`,
|
|
2237
2322
|
repairCommand: `git -C ${paths.worktree} status --short`,
|
|
2238
2323
|
preconditions
|
|
2239
2324
|
});
|
|
@@ -2249,30 +2334,8 @@ async function updateBlueprint(paths, options = {}, context = {}) {
|
|
|
2249
2334
|
});
|
|
2250
2335
|
}
|
|
2251
2336
|
|
|
2252
|
-
if (changedFiles.includes(
|
|
2253
|
-
|
|
2254
|
-
timeout: 15000
|
|
2255
|
-
});
|
|
2256
|
-
if (!addResult.ok) {
|
|
2257
|
-
return failSession(paths, {
|
|
2258
|
-
code: "blueprint_stage_failed",
|
|
2259
|
-
message: addResult.output || "Failed to stage app blueprint update.",
|
|
2260
|
-
repairCommand: `git -C ${paths.worktree} add .jskit/APP_BLUEPRINT.md`,
|
|
2261
|
-
preconditions
|
|
2262
|
-
});
|
|
2263
|
-
}
|
|
2264
|
-
const commitResult = await runGitInWorktree(paths.worktree, ["commit", "-m", `Update app blueprint for ${paths.sessionId}`], {
|
|
2265
|
-
timeout: 1000 * 60
|
|
2266
|
-
});
|
|
2267
|
-
if (!commitResult.ok) {
|
|
2268
|
-
return failSession(paths, {
|
|
2269
|
-
code: "blueprint_commit_failed",
|
|
2270
|
-
message: commitResult.output || "Failed to commit app blueprint update.",
|
|
2271
|
-
repairCommand: `git -C ${paths.worktree} status --short`,
|
|
2272
|
-
preconditions
|
|
2273
|
-
});
|
|
2274
|
-
}
|
|
2275
|
-
await writeReceipt(paths, "blueprint_updated", "Codex updated and JSKIT committed the app blueprint.");
|
|
2337
|
+
if (changedFiles.includes(BLUEPRINT_RELATIVE_PATH)) {
|
|
2338
|
+
await writeReceipt(paths, "blueprint_updated", "Codex updated the app blueprint; JSKIT will include it in the accepted changes commit.");
|
|
2276
2339
|
} else {
|
|
2277
2340
|
await writeReceipt(paths, "blueprint_updated", "Codex reviewed the app blueprint; no blueprint changes were needed.");
|
|
2278
2341
|
}
|
|
@@ -2283,6 +2346,7 @@ async function updateBlueprint(paths, options = {}, context = {}) {
|
|
|
2283
2346
|
});
|
|
2284
2347
|
}
|
|
2285
2348
|
|
|
2349
|
+
await writeBlueprintBaseline(paths);
|
|
2286
2350
|
const prompt = await renderPrompt(paths, "update_blueprint.md", {
|
|
2287
2351
|
agent_decisions_file: agentDecisionsPath,
|
|
2288
2352
|
app_blueprint_file: blueprintPath,
|