agentplane 0.2.25 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/assets/AGENTS.md +123 -526
- package/assets/agents/UPGRADER.json +10 -9
- package/assets/framework.manifest.json +112 -7
- package/assets/policy/check-routing.mjs +180 -0
- package/assets/policy/dod.code.md +25 -0
- package/assets/policy/dod.core.md +32 -0
- package/assets/policy/dod.docs.md +32 -0
- package/assets/policy/examples/migration-note.md +6 -0
- package/assets/policy/examples/pr-note.md +16 -0
- package/assets/policy/examples/unit-test-pattern.md +19 -0
- package/assets/policy/governance.md +37 -0
- package/assets/policy/incidents.md +36 -0
- package/assets/policy/security.must.md +7 -0
- package/assets/policy/workflow.branch_pr.md +34 -0
- package/assets/policy/workflow.direct.md +46 -0
- package/assets/policy/workflow.md +9 -0
- package/assets/policy/workflow.release.md +31 -0
- package/assets/policy/workflow.upgrade.md +20 -0
- package/bin/agentplane.js +47 -57
- package/bin/dist-guard.js +124 -0
- package/dist/.build-manifest.json +11 -0
- package/dist/agents/agents-template.d.ts +7 -0
- package/dist/agents/agents-template.d.ts.map +1 -1
- package/dist/agents/agents-template.js +41 -2
- package/dist/backends/task-backend/local-backend.d.ts +2 -0
- package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
- package/dist/backends/task-backend/local-backend.js +12 -1
- package/dist/backends/task-backend/redmine/mapping.d.ts.map +1 -1
- package/dist/backends/task-backend/redmine/mapping.js +26 -1
- package/dist/backends/task-backend/redmine-backend.d.ts +4 -0
- package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
- package/dist/backends/task-backend/redmine-backend.js +92 -9
- package/dist/backends/task-backend/shared/types.d.ts +1 -0
- package/dist/backends/task-backend/shared/types.d.ts.map +1 -1
- package/dist/backends/task-index.d.ts.map +1 -1
- package/dist/backends/task-index.js +8 -1
- package/dist/cli/command-guide.d.ts.map +1 -1
- package/dist/cli/command-guide.js +39 -17
- package/dist/cli/command-snippets.d.ts +24 -0
- package/dist/cli/command-snippets.d.ts.map +1 -0
- package/dist/cli/command-snippets.js +23 -0
- package/dist/cli/reason-codes.d.ts +9 -0
- package/dist/cli/reason-codes.d.ts.map +1 -0
- package/dist/cli/reason-codes.js +79 -0
- package/dist/cli/recipes-bundled.d.ts +1 -0
- package/dist/cli/recipes-bundled.d.ts.map +1 -1
- package/dist/cli/recipes-bundled.js +4 -1
- package/dist/cli/run-cli/command-catalog.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog.js +40 -1
- package/dist/cli/run-cli/commands/config.d.ts +5 -0
- package/dist/cli/run-cli/commands/config.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/config.js +86 -1
- package/dist/cli/run-cli/commands/core.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/core.js +57 -2
- package/dist/cli/run-cli/commands/ide.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/ide.js +8 -3
- package/dist/cli/run-cli/commands/init/recipes.d.ts +5 -1
- package/dist/cli/run-cli/commands/init/recipes.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/recipes.js +24 -4
- package/dist/cli/run-cli/commands/init/ui.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/ui.js +1 -2
- package/dist/cli/run-cli/commands/init/write-agents.d.ts +2 -0
- package/dist/cli/run-cli/commands/init/write-agents.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/write-agents.js +24 -5
- package/dist/cli/run-cli/commands/init/write-workflow.d.ts +12 -0
- package/dist/cli/run-cli/commands/init/write-workflow.d.ts.map +1 -0
- package/dist/cli/run-cli/commands/init/write-workflow.js +58 -0
- package/dist/cli/run-cli/commands/init.d.ts +4 -1
- package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init.js +126 -48
- package/dist/cli/run-cli.d.ts.map +1 -1
- package/dist/cli/run-cli.js +195 -8
- package/dist/commands/backend/sync.command.d.ts.map +1 -1
- package/dist/commands/backend/sync.command.js +7 -6
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +2 -0
- package/dist/commands/doctor.run.d.ts.map +1 -1
- package/dist/commands/doctor.run.js +107 -16
- package/dist/commands/guard/impl/commands.d.ts.map +1 -1
- package/dist/commands/guard/impl/commands.js +12 -6
- package/dist/commands/recipes/impl/commands/install.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/install.js +36 -13
- package/dist/commands/recipes/impl/scenario.d.ts.map +1 -1
- package/dist/commands/recipes/impl/scenario.js +25 -0
- package/dist/commands/recipes/impl/types.d.ts +4 -0
- package/dist/commands/recipes/impl/types.d.ts.map +1 -1
- package/dist/commands/release/apply.command.d.ts.map +1 -1
- package/dist/commands/release/apply.command.js +9 -4
- package/dist/commands/release/plan.command.d.ts.map +1 -1
- package/dist/commands/release/plan.command.js +9 -3
- package/dist/commands/scenario/impl/commands.d.ts.map +1 -1
- package/dist/commands/scenario/impl/commands.js +74 -3
- package/dist/commands/scenario/impl/report.d.ts +8 -0
- package/dist/commands/scenario/impl/report.d.ts.map +1 -1
- package/dist/commands/scenario/impl/report.js +1 -0
- package/dist/commands/shared/reconcile-check.d.ts +7 -0
- package/dist/commands/shared/reconcile-check.d.ts.map +1 -0
- package/dist/commands/shared/reconcile-check.js +60 -0
- package/dist/commands/sync.command.d.ts.map +1 -1
- package/dist/commands/sync.command.js +9 -2
- package/dist/commands/task/add.d.ts.map +1 -1
- package/dist/commands/task/add.js +32 -0
- package/dist/commands/task/doc.command.d.ts.map +1 -1
- package/dist/commands/task/doc.command.js +1 -0
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +11 -1
- package/dist/commands/task/list.d.ts.map +1 -1
- package/dist/commands/task/list.js +2 -1
- package/dist/commands/task/list.spec.d.ts.map +1 -1
- package/dist/commands/task/list.spec.js +7 -0
- package/dist/commands/task/new.d.ts.map +1 -1
- package/dist/commands/task/new.js +41 -4
- package/dist/commands/task/next.d.ts.map +1 -1
- package/dist/commands/task/next.js +2 -1
- package/dist/commands/task/next.spec.d.ts.map +1 -1
- package/dist/commands/task/next.spec.js +7 -0
- package/dist/commands/task/plan.d.ts.map +1 -1
- package/dist/commands/task/plan.js +7 -1
- package/dist/commands/task/search.d.ts.map +1 -1
- package/dist/commands/task/search.js +2 -1
- package/dist/commands/task/search.spec.d.ts.map +1 -1
- package/dist/commands/task/search.spec.js +7 -0
- package/dist/commands/task/shared.d.ts +14 -0
- package/dist/commands/task/shared.d.ts.map +1 -1
- package/dist/commands/task/shared.js +58 -1
- package/dist/commands/task/start-ready.js +1 -1
- package/dist/commands/task/verify-record.d.ts.map +1 -1
- package/dist/commands/task/verify-record.js +2 -0
- package/dist/commands/upgrade.command.d.ts.map +1 -1
- package/dist/commands/upgrade.command.js +2 -2
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +263 -294
- package/dist/commands/workflow-build.command.d.ts +8 -0
- package/dist/commands/workflow-build.command.d.ts.map +1 -0
- package/dist/commands/workflow-build.command.js +103 -0
- package/dist/commands/workflow-playbook.command.d.ts +10 -0
- package/dist/commands/workflow-playbook.command.d.ts.map +1 -0
- package/dist/commands/workflow-playbook.command.js +173 -0
- package/dist/commands/workflow-restore.command.d.ts +5 -0
- package/dist/commands/workflow-restore.command.d.ts.map +1 -0
- package/dist/commands/workflow-restore.command.js +30 -0
- package/dist/commands/workflow.command.d.ts +6 -0
- package/dist/commands/workflow.command.d.ts.map +1 -0
- package/dist/commands/workflow.command.js +36 -0
- package/dist/harness/dynamic-tool-contract.d.ts +29 -0
- package/dist/harness/dynamic-tool-contract.d.ts.map +1 -0
- package/dist/harness/dynamic-tool-contract.js +86 -0
- package/dist/harness/hooks-lifecycle.d.ts +27 -0
- package/dist/harness/hooks-lifecycle.d.ts.map +1 -0
- package/dist/harness/hooks-lifecycle.js +67 -0
- package/dist/harness/index.d.ts +9 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +8 -0
- package/dist/harness/reconcile.d.ts +37 -0
- package/dist/harness/reconcile.d.ts.map +1 -0
- package/dist/harness/reconcile.js +42 -0
- package/dist/harness/retry-policy.d.ts +31 -0
- package/dist/harness/retry-policy.d.ts.map +1 -0
- package/dist/harness/retry-policy.js +33 -0
- package/dist/harness/scheduler.d.ts +18 -0
- package/dist/harness/scheduler.d.ts.map +1 -0
- package/dist/harness/scheduler.js +55 -0
- package/dist/harness/state-machine.d.ts +17 -0
- package/dist/harness/state-machine.d.ts.map +1 -0
- package/dist/harness/state-machine.js +70 -0
- package/dist/harness/token-accounting.d.ts +19 -0
- package/dist/harness/token-accounting.d.ts.map +1 -0
- package/dist/harness/token-accounting.js +77 -0
- package/dist/harness/workspace-safety.d.ts +14 -0
- package/dist/harness/workspace-safety.d.ts.map +1 -0
- package/dist/harness/workspace-safety.js +62 -0
- package/dist/recipes/bundled-recipes.d.ts +4 -0
- package/dist/recipes/bundled-recipes.d.ts.map +1 -1
- package/dist/recipes/bundled-recipes.js +11 -0
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +1 -0
- package/dist/shared/policy-gateway.d.ts +15 -0
- package/dist/shared/policy-gateway.d.ts.map +1 -0
- package/dist/shared/policy-gateway.js +49 -0
- package/dist/shared/protected-paths.d.ts.map +1 -1
- package/dist/shared/protected-paths.js +1 -0
- package/dist/shared/runtime-artifacts.d.ts +2 -2
- package/dist/shared/runtime-artifacts.d.ts.map +1 -1
- package/dist/shared/runtime-artifacts.js +4 -0
- package/dist/workflow-runtime/build.d.ts +4 -0
- package/dist/workflow-runtime/build.d.ts.map +1 -0
- package/dist/workflow-runtime/build.js +126 -0
- package/dist/workflow-runtime/enforcement.d.ts +3 -0
- package/dist/workflow-runtime/enforcement.d.ts.map +1 -0
- package/dist/workflow-runtime/enforcement.js +10 -0
- package/dist/workflow-runtime/file-ops.d.ts +11 -0
- package/dist/workflow-runtime/file-ops.d.ts.map +1 -0
- package/dist/workflow-runtime/file-ops.js +248 -0
- package/dist/workflow-runtime/fix.d.ts +9 -0
- package/dist/workflow-runtime/fix.d.ts.map +1 -0
- package/dist/workflow-runtime/fix.js +107 -0
- package/dist/workflow-runtime/index.d.ts +11 -0
- package/dist/workflow-runtime/index.d.ts.map +1 -0
- package/dist/workflow-runtime/index.js +10 -0
- package/dist/workflow-runtime/markdown.d.ts +10 -0
- package/dist/workflow-runtime/markdown.d.ts.map +1 -0
- package/dist/workflow-runtime/markdown.js +147 -0
- package/dist/workflow-runtime/observability.d.ts +12 -0
- package/dist/workflow-runtime/observability.d.ts.map +1 -0
- package/dist/workflow-runtime/observability.js +14 -0
- package/dist/workflow-runtime/paths.d.ts +3 -0
- package/dist/workflow-runtime/paths.d.ts.map +1 -0
- package/dist/workflow-runtime/paths.js +11 -0
- package/dist/workflow-runtime/template.d.ts +7 -0
- package/dist/workflow-runtime/template.d.ts.map +1 -0
- package/dist/workflow-runtime/template.js +94 -0
- package/dist/workflow-runtime/types.d.ts +68 -0
- package/dist/workflow-runtime/types.d.ts.map +1 -0
- package/dist/workflow-runtime/types.js +1 -0
- package/dist/workflow-runtime/validate.d.ts +8 -0
- package/dist/workflow-runtime/validate.d.ts.map +1 -0
- package/dist/workflow-runtime/validate.js +331 -0
- package/package.json +3 -3
package/dist/commands/upgrade.js
CHANGED
|
@@ -11,6 +11,8 @@ import { invalidFieldMessage, invalidValueMessage, requiredFieldMessage, warnMes
|
|
|
11
11
|
import { exitCodeForError } from "../cli/exit-codes.js";
|
|
12
12
|
import { CliError } from "../shared/errors.js";
|
|
13
13
|
import { ensureNetworkApproved } from "./shared/network-approval.js";
|
|
14
|
+
import { execFileAsync, gitEnv } from "./shared/git.js";
|
|
15
|
+
import { getVersion } from "../meta/version.js";
|
|
14
16
|
const DEFAULT_UPGRADE_ASSET = "agentplane-upgrade.tar.gz";
|
|
15
17
|
const DEFAULT_UPGRADE_CHECKSUM_ASSET = "agentplane-upgrade.tar.gz.sha256";
|
|
16
18
|
const UPGRADE_DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
@@ -148,83 +150,22 @@ async function resolveUpgradeRoot(extractedDir) {
|
|
|
148
150
|
function isAllowedUpgradePath(relPath) {
|
|
149
151
|
if (relPath === "AGENTS.md")
|
|
150
152
|
return true;
|
|
153
|
+
if (relPath === "CLAUDE.md")
|
|
154
|
+
return true;
|
|
151
155
|
if (relPath.startsWith(".agentplane/agents/") && relPath.endsWith(".json"))
|
|
152
156
|
return true;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const end = text.indexOf(LOCAL_OVERRIDES_END);
|
|
160
|
-
if (start === -1 || end === -1 || end < start)
|
|
161
|
-
return null;
|
|
162
|
-
return text.slice(start + LOCAL_OVERRIDES_START.length, end).trim();
|
|
163
|
-
}
|
|
164
|
-
function withLocalOverridesBlock(base, localOverrides) {
|
|
165
|
-
const start = base.indexOf(LOCAL_OVERRIDES_START);
|
|
166
|
-
const end = base.indexOf(LOCAL_OVERRIDES_END);
|
|
167
|
-
if (start === -1 || end === -1 || end < start) {
|
|
168
|
-
const suffix = "\n\n## Local Overrides (preserved across upgrades)\n\n" +
|
|
169
|
-
`${LOCAL_OVERRIDES_START}\n` +
|
|
170
|
-
(localOverrides.trim() ? `${localOverrides.trim()}\n` : "") +
|
|
171
|
-
`${LOCAL_OVERRIDES_END}\n`;
|
|
172
|
-
return `${base.trimEnd()}${suffix}`;
|
|
173
|
-
}
|
|
174
|
-
const before = base.slice(0, start + LOCAL_OVERRIDES_START.length);
|
|
175
|
-
const after = base.slice(end);
|
|
176
|
-
return `${before}\n${localOverrides.trim() ? `${localOverrides.trim()}\n` : ""}${after}`;
|
|
177
|
-
}
|
|
178
|
-
function parseH2Sections(text) {
|
|
179
|
-
const lines = text.replaceAll("\r\n", "\n").split("\n");
|
|
180
|
-
const sections = new Map();
|
|
181
|
-
let current = null;
|
|
182
|
-
let buf = [];
|
|
183
|
-
const flush = () => {
|
|
184
|
-
if (!current)
|
|
185
|
-
return;
|
|
186
|
-
if (!sections.has(current)) {
|
|
187
|
-
sections.set(current, buf.join("\n").trimEnd());
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
for (const line of lines) {
|
|
191
|
-
const m = /^##\s+(.+?)\s*$/.exec(line);
|
|
192
|
-
if (m) {
|
|
193
|
-
flush();
|
|
194
|
-
current = (m[1] ?? "").trim();
|
|
195
|
-
buf = [];
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
if (current)
|
|
199
|
-
buf.push(line);
|
|
200
|
-
}
|
|
201
|
-
flush();
|
|
202
|
-
return sections;
|
|
203
|
-
}
|
|
204
|
-
function mergeAgentsPolicyMarkdown(incoming, current) {
|
|
205
|
-
const local = extractLocalOverridesBlock(current);
|
|
206
|
-
if (local !== null) {
|
|
207
|
-
return withLocalOverridesBlock(incoming, local);
|
|
208
|
-
}
|
|
209
|
-
// Fallback: if the user edited AGENTS.md without the local markers, preserve their changes by
|
|
210
|
-
// appending differing/extra sections into a dedicated local overrides block.
|
|
211
|
-
const incomingSections = parseH2Sections(incoming);
|
|
212
|
-
const currentSections = parseH2Sections(current);
|
|
213
|
-
const overrides = [];
|
|
214
|
-
for (const [title, body] of currentSections.entries()) {
|
|
215
|
-
const incomingBody = incomingSections.get(title);
|
|
216
|
-
if (incomingBody === undefined) {
|
|
217
|
-
overrides.push(`### Added section: ${title}\n\n${body.trim()}\n`);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (incomingBody.trim() !== body.trim()) {
|
|
221
|
-
overrides.push(`### Local edits for: ${title}\n\n${body.trim()}\n`);
|
|
222
|
-
}
|
|
157
|
+
if (relPath.startsWith(".agentplane/policy/") &&
|
|
158
|
+
(relPath.endsWith(".md") ||
|
|
159
|
+
relPath.endsWith(".ts") ||
|
|
160
|
+
relPath.endsWith(".js") ||
|
|
161
|
+
relPath.endsWith(".mjs"))) {
|
|
162
|
+
return true;
|
|
223
163
|
}
|
|
224
|
-
|
|
225
|
-
return incoming;
|
|
226
|
-
return withLocalOverridesBlock(incoming, overrides.join("\n"));
|
|
164
|
+
return false;
|
|
227
165
|
}
|
|
166
|
+
const INCIDENTS_POLICY_PATH = ".agentplane/policy/incidents.md";
|
|
167
|
+
const INCIDENTS_APPEND_MARKER = "<!-- AGENTPLANE:UPGRADE-APPEND incidents.md -->";
|
|
168
|
+
const CONFIG_REL_PATH = ".agentplane/config.json";
|
|
228
169
|
function isJsonRecord(value) {
|
|
229
170
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
230
171
|
}
|
|
@@ -262,127 +203,167 @@ function textChangedForType(opts) {
|
|
|
262
203
|
}
|
|
263
204
|
return opts.aText.trimEnd() !== opts.bText.trimEnd();
|
|
264
205
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
incoming = JSON.parse(incomingText);
|
|
272
|
-
current = JSON.parse(currentText);
|
|
206
|
+
function parseIncidentEntryBlocks(entriesBody) {
|
|
207
|
+
const lines = entriesBody.replaceAll("\r\n", "\n").split("\n");
|
|
208
|
+
const starts = [];
|
|
209
|
+
for (const [index, line] of lines.entries()) {
|
|
210
|
+
if (/^\s*-\s*id:\s+/i.test(line ?? ""))
|
|
211
|
+
starts.push(index);
|
|
273
212
|
}
|
|
274
|
-
|
|
275
|
-
|
|
213
|
+
const blocks = [];
|
|
214
|
+
for (const [idx, start] of starts.entries()) {
|
|
215
|
+
const end = starts.at(idx + 1) ?? lines.length;
|
|
216
|
+
const slice = lines.slice(start, end);
|
|
217
|
+
while (slice.length > 0 && !(slice[0] ?? "").trim())
|
|
218
|
+
slice.shift();
|
|
219
|
+
while (slice.length > 0 && !(slice.at(-1) ?? "").trim())
|
|
220
|
+
slice.pop();
|
|
221
|
+
const block = slice.join("\n").trim();
|
|
222
|
+
if (block)
|
|
223
|
+
blocks.push(block);
|
|
276
224
|
}
|
|
277
|
-
|
|
225
|
+
return blocks;
|
|
226
|
+
}
|
|
227
|
+
function normalizeEntryBlock(block) {
|
|
228
|
+
return block
|
|
229
|
+
.replaceAll("\r\n", "\n")
|
|
230
|
+
.split("\n")
|
|
231
|
+
.map((line) => line.trimEnd())
|
|
232
|
+
.join("\n")
|
|
233
|
+
.trim();
|
|
234
|
+
}
|
|
235
|
+
function splitEntriesSection(text) {
|
|
236
|
+
const lines = text.replaceAll("\r\n", "\n").split("\n");
|
|
237
|
+
const headingIndex = lines.findIndex((line) => /^\s*##\s+Entries\s*$/i.test(line));
|
|
238
|
+
if (headingIndex === -1)
|
|
278
239
|
return null;
|
|
279
|
-
|
|
280
|
-
for (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
if (Array.isArray(incVal) && Array.isArray(curVal)) {
|
|
287
|
-
const merged = [...incVal];
|
|
288
|
-
const seen = new Set();
|
|
289
|
-
for (const x of merged)
|
|
290
|
-
seen.add(JSON.stringify(canonicalizeJson(x)));
|
|
291
|
-
for (const item of curVal) {
|
|
292
|
-
const key = JSON.stringify(canonicalizeJson(item));
|
|
293
|
-
if (!seen.has(key)) {
|
|
294
|
-
merged.push(item);
|
|
295
|
-
seen.add(key);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
out[k] = merged;
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
if (isJsonRecord(incVal) && isJsonRecord(curVal)) {
|
|
302
|
-
// Preserve user-only subkeys but let upstream win for conflicts.
|
|
303
|
-
out[k] = { ...curVal, ...incVal };
|
|
304
|
-
continue;
|
|
240
|
+
let nextHeadingIndex = lines.length;
|
|
241
|
+
for (let i = headingIndex + 1; i < lines.length; i++) {
|
|
242
|
+
if (/^\s*##\s+/.test(lines[i] ?? "")) {
|
|
243
|
+
nextHeadingIndex = i;
|
|
244
|
+
break;
|
|
305
245
|
}
|
|
306
|
-
out[k] = incVal;
|
|
307
246
|
}
|
|
308
|
-
return
|
|
247
|
+
return {
|
|
248
|
+
before: lines.slice(0, headingIndex + 1).join("\n"),
|
|
249
|
+
entriesBody: lines.slice(headingIndex + 1, nextHeadingIndex).join("\n"),
|
|
250
|
+
after: lines.slice(nextHeadingIndex).join("\n"),
|
|
251
|
+
};
|
|
309
252
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
253
|
+
function mergeIncidentsPolicy(opts) {
|
|
254
|
+
const incomingTrimmed = opts.incomingText.trim();
|
|
255
|
+
if (!incomingTrimmed)
|
|
256
|
+
return { nextText: opts.currentText, appended: false, appendedCount: 0 };
|
|
257
|
+
const incomingSection = splitEntriesSection(opts.incomingText);
|
|
258
|
+
const currentSection = splitEntriesSection(opts.currentText);
|
|
259
|
+
if (!incomingSection || !currentSection) {
|
|
260
|
+
return { nextText: opts.incomingText, appended: false, appendedCount: 0 };
|
|
318
261
|
}
|
|
319
|
-
|
|
320
|
-
|
|
262
|
+
const incomingBlocks = parseIncidentEntryBlocks(incomingSection.entriesBody).map((block) => normalizeEntryBlock(block));
|
|
263
|
+
const currentBlocks = parseIncidentEntryBlocks(currentSection.entriesBody).map((block) => normalizeEntryBlock(block));
|
|
264
|
+
if (currentBlocks.length === 0) {
|
|
265
|
+
return { nextText: opts.incomingText, appended: false, appendedCount: 0 };
|
|
266
|
+
}
|
|
267
|
+
const baselineSection = opts.baselineText ? splitEntriesSection(opts.baselineText) : null;
|
|
268
|
+
const baselineBlocks = baselineSection
|
|
269
|
+
? parseIncidentEntryBlocks(baselineSection.entriesBody).map((block) => normalizeEntryBlock(block))
|
|
270
|
+
: [];
|
|
271
|
+
const baselineSet = new Set(baselineBlocks);
|
|
272
|
+
const incomingSet = new Set(incomingBlocks);
|
|
273
|
+
const userAdded = currentBlocks.filter((block) => {
|
|
274
|
+
if (baselineSet.size > 0 && baselineSet.has(block))
|
|
275
|
+
return false;
|
|
276
|
+
return true;
|
|
277
|
+
});
|
|
278
|
+
const toAppend = userAdded.filter((block) => !incomingSet.has(block));
|
|
279
|
+
if (toAppend.length === 0) {
|
|
280
|
+
return { nextText: opts.incomingText, appended: false, appendedCount: 0 };
|
|
321
281
|
}
|
|
322
|
-
|
|
282
|
+
const mergedBlocks = [...incomingBlocks, ...toAppend];
|
|
283
|
+
const renderedEntries = mergedBlocks.length > 0 ? `\n\n${mergedBlocks.join("\n\n")}\n` : "\n\n- None yet.\n";
|
|
284
|
+
const afterSuffix = incomingSection.after ? `\n${incomingSection.after.trimStart()}` : "";
|
|
285
|
+
const nextText = `${incomingSection.before.trimEnd()}` +
|
|
286
|
+
`${renderedEntries}` +
|
|
287
|
+
`${INCIDENTS_APPEND_MARKER}\n` +
|
|
288
|
+
`${afterSuffix}` +
|
|
289
|
+
`\n`;
|
|
290
|
+
return { nextText, appended: true, appendedCount: toAppend.length };
|
|
291
|
+
}
|
|
292
|
+
function normalizeUpgradeVersionLabel(input) {
|
|
293
|
+
const trimmed = input.trim();
|
|
294
|
+
if (!trimmed)
|
|
295
|
+
return "unknown";
|
|
296
|
+
if (/^v\d/i.test(trimmed))
|
|
297
|
+
return trimmed;
|
|
298
|
+
return `v${trimmed}`;
|
|
299
|
+
}
|
|
300
|
+
async function ensureCleanTrackedTreeForUpgrade(gitRoot) {
|
|
301
|
+
const { stdout } = await execFileAsync("git", ["status", "--short", "--untracked-files=no"], {
|
|
302
|
+
cwd: gitRoot,
|
|
303
|
+
env: gitEnv(),
|
|
304
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
305
|
+
});
|
|
306
|
+
const dirty = String(stdout ?? "")
|
|
307
|
+
.split(/\r?\n/u)
|
|
308
|
+
.map((line) => line.trimEnd())
|
|
309
|
+
.filter((line) => line.length > 0);
|
|
310
|
+
if (dirty.length === 0)
|
|
311
|
+
return;
|
|
312
|
+
throw new CliError({
|
|
313
|
+
exitCode: exitCodeForError("E_GIT"),
|
|
314
|
+
code: "E_GIT",
|
|
315
|
+
message: "Upgrade --auto requires a clean tracked working tree.\n" +
|
|
316
|
+
`Found tracked changes:\n${dirty.map((line) => ` ${line}`).join("\n")}`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async function createUpgradeCommit(opts) {
|
|
320
|
+
const uniquePaths = [...new Set(opts.paths.filter(Boolean))];
|
|
321
|
+
if (uniquePaths.length === 0)
|
|
323
322
|
return null;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (userChanged)
|
|
363
|
-
merged[sk] = curSub;
|
|
364
|
-
else if (incSub !== undefined)
|
|
365
|
-
merged[sk] = incSub;
|
|
366
|
-
else if (curSub !== undefined)
|
|
367
|
-
merged[sk] = curSub;
|
|
368
|
-
}
|
|
369
|
-
out[key] = merged;
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
// Scalars: prefer incoming unless the user changed vs base.
|
|
373
|
-
if (!jsonEqual(curVal, baseVal)) {
|
|
374
|
-
if (curVal !== undefined)
|
|
375
|
-
out[key] = curVal;
|
|
376
|
-
else if (incVal !== undefined)
|
|
377
|
-
out[key] = incVal;
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
if (incVal !== undefined)
|
|
381
|
-
out[key] = incVal;
|
|
382
|
-
else if (curVal !== undefined)
|
|
383
|
-
out[key] = curVal;
|
|
323
|
+
await execFileAsync("git", ["add", "--", ...uniquePaths], {
|
|
324
|
+
cwd: opts.gitRoot,
|
|
325
|
+
env: gitEnv(),
|
|
326
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
327
|
+
});
|
|
328
|
+
const { stdout: stagedOut } = await execFileAsync("git", ["diff", "--cached", "--name-only", "-z"], {
|
|
329
|
+
cwd: opts.gitRoot,
|
|
330
|
+
env: gitEnv(),
|
|
331
|
+
encoding: "buffer",
|
|
332
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
333
|
+
});
|
|
334
|
+
const staged = (Buffer.isBuffer(stagedOut) ? stagedOut.toString("utf8") : String(stagedOut ?? ""))
|
|
335
|
+
.split("\0")
|
|
336
|
+
.map((entry) => entry.trim())
|
|
337
|
+
.some(Boolean);
|
|
338
|
+
if (!staged)
|
|
339
|
+
return null;
|
|
340
|
+
const subject = `⬆️ upgrade: apply framework ${opts.versionLabel}`;
|
|
341
|
+
const body = `Upgrade-Version: ${opts.versionLabel}\n` +
|
|
342
|
+
`Source: ${opts.source}\n` +
|
|
343
|
+
`Managed-Changes: add=${opts.additions}, update=${opts.updates}, unchanged=${opts.unchanged}\n` +
|
|
344
|
+
`Incidents-Appended: ${opts.incidentsAppendedCount}\n`;
|
|
345
|
+
try {
|
|
346
|
+
await execFileAsync("git", ["commit", "-m", subject, "-m", body], {
|
|
347
|
+
cwd: opts.gitRoot,
|
|
348
|
+
env: gitEnv(),
|
|
349
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
const details = err?.stderr ?? "";
|
|
354
|
+
throw new CliError({
|
|
355
|
+
exitCode: exitCodeForError("E_GIT"),
|
|
356
|
+
code: "E_GIT",
|
|
357
|
+
message: "Upgrade applied but failed to create the upgrade commit.\n" +
|
|
358
|
+
"Fix commit policy/hook issues and commit the staged upgrade files as a dedicated upgrade commit.\n" +
|
|
359
|
+
(String(details).trim() ? `Details:\n${String(details).trim()}` : ""),
|
|
360
|
+
});
|
|
384
361
|
}
|
|
385
|
-
|
|
362
|
+
const { stdout: hashOut } = await execFileAsync("git", ["rev-parse", "HEAD"], {
|
|
363
|
+
cwd: opts.gitRoot,
|
|
364
|
+
env: gitEnv(),
|
|
365
|
+
});
|
|
366
|
+
return { hash: String(hashOut ?? "").trim(), subject };
|
|
386
367
|
}
|
|
387
368
|
export async function cmdUpgradeParsed(opts) {
|
|
388
369
|
const flags = opts.flags;
|
|
@@ -400,6 +381,9 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
400
381
|
rootOverride: opts.rootOverride ?? null,
|
|
401
382
|
});
|
|
402
383
|
const loaded = await loadConfig(resolved.agentplaneDir);
|
|
384
|
+
if (flags.mode === "auto" && !flags.dryRun) {
|
|
385
|
+
await ensureCleanTrackedTreeForUpgrade(resolved.gitRoot);
|
|
386
|
+
}
|
|
403
387
|
const upgradeStateDir = path.join(resolved.agentplaneDir, ".upgrade");
|
|
404
388
|
const lockPath = path.join(upgradeStateDir, "lock.json");
|
|
405
389
|
const statePath = path.join(upgradeStateDir, "state.json");
|
|
@@ -438,6 +422,7 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
438
422
|
let bundleLayout = "upgrade_bundle";
|
|
439
423
|
let bundleRoot = "";
|
|
440
424
|
let normalizedSourceToPersist = null;
|
|
425
|
+
let upgradeVersionLabel = normalizeUpgradeVersionLabel(getVersion());
|
|
441
426
|
if (!hasBundle && !useRemote) {
|
|
442
427
|
bundleLayout = "local_assets";
|
|
443
428
|
bundleRoot = fileURLToPath(ASSETS_DIR_URL);
|
|
@@ -477,6 +462,12 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
477
462
|
const assetName = flags.asset ?? DEFAULT_UPGRADE_ASSET;
|
|
478
463
|
const checksumName = flags.checksumAsset ?? DEFAULT_UPGRADE_CHECKSUM_ASSET;
|
|
479
464
|
const release = (await fetchJson(releaseUrl, UPGRADE_RELEASE_METADATA_TIMEOUT_MS));
|
|
465
|
+
const releaseTag = (typeof release.tag_name === "string" && release.tag_name.trim()) ||
|
|
466
|
+
(typeof flags.tag === "string" && flags.tag.trim()) ||
|
|
467
|
+
"";
|
|
468
|
+
if (releaseTag) {
|
|
469
|
+
upgradeVersionLabel = normalizeUpgradeVersionLabel(releaseTag);
|
|
470
|
+
}
|
|
480
471
|
const download = resolveUpgradeDownloadFromRelease({
|
|
481
472
|
release,
|
|
482
473
|
owner,
|
|
@@ -553,7 +544,7 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
553
544
|
const merged = [];
|
|
554
545
|
const missingRequired = [];
|
|
555
546
|
const reviewRecords = [];
|
|
556
|
-
|
|
547
|
+
let incidentsAppendedCount = 0;
|
|
557
548
|
const readBaselineText = async (baselineKey) => {
|
|
558
549
|
try {
|
|
559
550
|
return await readFile(path.join(baselineDirNew, baselineKey), "utf8");
|
|
@@ -571,33 +562,46 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
571
562
|
const toBaselineKey = (rel) => {
|
|
572
563
|
if (rel === "AGENTS.md")
|
|
573
564
|
return "AGENTS.md";
|
|
565
|
+
if (rel === "CLAUDE.md")
|
|
566
|
+
return "CLAUDE.md";
|
|
574
567
|
if (rel.startsWith(".agentplane/"))
|
|
575
568
|
return rel.slice(".agentplane/".length);
|
|
576
569
|
return null;
|
|
577
570
|
};
|
|
571
|
+
const policyGatewayRel = (await fileExists(path.join(resolved.gitRoot, "AGENTS.md")))
|
|
572
|
+
? "AGENTS.md"
|
|
573
|
+
: (await fileExists(path.join(resolved.gitRoot, "CLAUDE.md")))
|
|
574
|
+
? "CLAUDE.md"
|
|
575
|
+
: "AGENTS.md";
|
|
576
|
+
const remapManagedGatewayRel = (rel) => {
|
|
577
|
+
if (rel === "AGENTS.md" && policyGatewayRel === "CLAUDE.md")
|
|
578
|
+
return "CLAUDE.md";
|
|
579
|
+
return rel;
|
|
580
|
+
};
|
|
578
581
|
for (const entry of manifest.files) {
|
|
579
|
-
const
|
|
580
|
-
if (!
|
|
582
|
+
const relRaw = entry.path.replaceAll("\\", "/").trim();
|
|
583
|
+
if (!relRaw || relRaw.startsWith("..") || path.isAbsolute(relRaw)) {
|
|
581
584
|
throw new CliError({
|
|
582
585
|
exitCode: 3,
|
|
583
586
|
code: "E_VALIDATION",
|
|
584
587
|
message: `Invalid manifest path: ${entry.path}`,
|
|
585
588
|
});
|
|
586
589
|
}
|
|
587
|
-
if (isDeniedUpgradePath(
|
|
590
|
+
if (isDeniedUpgradePath(relRaw)) {
|
|
588
591
|
throw new CliError({
|
|
589
592
|
exitCode: 3,
|
|
590
593
|
code: "E_VALIDATION",
|
|
591
|
-
message: `Manifest includes a denied path: ${
|
|
594
|
+
message: `Manifest includes a denied path: ${relRaw}`,
|
|
592
595
|
});
|
|
593
596
|
}
|
|
594
|
-
if (!isAllowedUpgradePath(
|
|
597
|
+
if (!isAllowedUpgradePath(relRaw)) {
|
|
595
598
|
throw new CliError({
|
|
596
599
|
exitCode: 3,
|
|
597
600
|
code: "E_VALIDATION",
|
|
598
|
-
message: `Manifest path not allowed: ${
|
|
601
|
+
message: `Manifest path not allowed: ${relRaw}`,
|
|
599
602
|
});
|
|
600
603
|
}
|
|
604
|
+
const rel = remapManagedGatewayRel(relRaw);
|
|
601
605
|
const destPath = path.join(resolved.gitRoot, rel);
|
|
602
606
|
const kind = await getPathKind(destPath);
|
|
603
607
|
if (kind === "dir") {
|
|
@@ -607,16 +611,27 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
607
611
|
message: `Upgrade target is a directory: ${rel}`,
|
|
608
612
|
});
|
|
609
613
|
}
|
|
610
|
-
const sourceRel = (entry.source_path ?? entry.path).replaceAll("\\", "/").trim();
|
|
611
|
-
const sourcePath = path.join(bundleRoot, sourceRel);
|
|
612
614
|
let data;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
615
|
+
{
|
|
616
|
+
const sourceRelRaw = (entry.source_path ?? entry.path).replaceAll("\\", "/").trim();
|
|
617
|
+
const mappedSourceRel = rel === "CLAUDE.md" && sourceRelRaw === "AGENTS.md" ? "CLAUDE.md" : sourceRelRaw;
|
|
618
|
+
const sourceCandidates = [...new Set([mappedSourceRel, sourceRelRaw])];
|
|
619
|
+
let loaded = null;
|
|
620
|
+
for (const candidate of sourceCandidates) {
|
|
621
|
+
try {
|
|
622
|
+
loaded = await readFile(path.join(bundleRoot, candidate));
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
// try next candidate
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (!loaded) {
|
|
630
|
+
if (entry.required)
|
|
631
|
+
missingRequired.push(rel);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
data = loaded;
|
|
620
635
|
}
|
|
621
636
|
let existingBuf = null;
|
|
622
637
|
let existingText = null;
|
|
@@ -649,7 +664,7 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
649
664
|
aText: currentTextForReview,
|
|
650
665
|
bText: incomingTextOriginal,
|
|
651
666
|
}) === false;
|
|
652
|
-
// Fast-path:
|
|
667
|
+
// Fast-path: incoming already equals local.
|
|
653
668
|
if (currentTextForReview !== null && currentAndIncomingEqual) {
|
|
654
669
|
skipped.push(rel);
|
|
655
670
|
reviewRecords.push({
|
|
@@ -665,7 +680,7 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
665
680
|
});
|
|
666
681
|
continue;
|
|
667
682
|
}
|
|
668
|
-
// No local edits vs baseline: file can be safely replaced with incoming
|
|
683
|
+
// No local edits vs baseline: file can be safely replaced with incoming.
|
|
669
684
|
if (currentTextForReview !== null && changedCurrentVsBaseline === false) {
|
|
670
685
|
updates.push(rel);
|
|
671
686
|
fileContents.set(rel, data);
|
|
@@ -684,53 +699,24 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
684
699
|
}
|
|
685
700
|
let mergeApplied = false;
|
|
686
701
|
let mergePath = "none";
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
702
|
+
// Simplified policy for upgrade:
|
|
703
|
+
// - All managed files are replaced with incoming bundle content.
|
|
704
|
+
// - incidents.md is append-only when local file already has content.
|
|
705
|
+
if (existingBuf && rel === INCIDENTS_POLICY_PATH) {
|
|
706
|
+
existingText = existingBuf.toString("utf8");
|
|
707
|
+
const mergedIncidents = mergeIncidentsPolicy({
|
|
708
|
+
incomingText: data.toString("utf8"),
|
|
709
|
+
currentText: existingText,
|
|
710
|
+
baselineText,
|
|
711
|
+
});
|
|
712
|
+
data = Buffer.from(mergedIncidents.nextText, "utf8");
|
|
713
|
+
if (mergedIncidents.appended) {
|
|
693
714
|
merged.push(rel);
|
|
694
715
|
mergeApplied = true;
|
|
695
|
-
mergePath = "
|
|
696
|
-
|
|
697
|
-
else if (entry.merge_strategy === "agent_json_3way" &&
|
|
698
|
-
rel.startsWith(".agentplane/agents/") &&
|
|
699
|
-
rel.endsWith(".json")) {
|
|
700
|
-
existingText = existingBuf.toString("utf8");
|
|
701
|
-
let mergedText = null;
|
|
702
|
-
if (baselineText !== null) {
|
|
703
|
-
try {
|
|
704
|
-
mergedText = mergeAgentJson3Way({
|
|
705
|
-
incomingText: data.toString("utf8"),
|
|
706
|
-
currentText: existingText,
|
|
707
|
-
baseText: baselineText,
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
catch {
|
|
711
|
-
mergedText = null;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
if (mergedText) {
|
|
715
|
-
mergePath = "3way";
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
mergedText = mergeAgentJsonIncomingWins(data.toString("utf8"), existingText);
|
|
719
|
-
if (mergedText) {
|
|
720
|
-
mergePath = baselineText === null ? "incomingWins" : "incomingWinsFallback";
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
if (mergedText) {
|
|
724
|
-
data = Buffer.from(mergedText, "utf8");
|
|
725
|
-
merged.push(rel);
|
|
726
|
-
mergeApplied = true;
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
mergePath = "parseFailed";
|
|
730
|
-
}
|
|
716
|
+
mergePath = "incidentsAppend";
|
|
717
|
+
incidentsAppendedCount += mergedIncidents.appendedCount;
|
|
731
718
|
}
|
|
732
719
|
}
|
|
733
|
-
const proposedText = data.toString("utf8");
|
|
734
720
|
const currentDiffersFromIncoming = currentTextForReview === null
|
|
735
721
|
? false
|
|
736
722
|
: textChangedForType({
|
|
@@ -738,16 +724,7 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
738
724
|
aText: currentTextForReview,
|
|
739
725
|
bText: incomingTextOriginal,
|
|
740
726
|
});
|
|
741
|
-
const
|
|
742
|
-
? false
|
|
743
|
-
: currentDiffersFromIncoming &&
|
|
744
|
-
Boolean(changedCurrentVsBaseline) &&
|
|
745
|
-
Boolean(changedIncomingVsBaseline);
|
|
746
|
-
const unresolvedLocalEditsConflict = baselineText === null
|
|
747
|
-
? false
|
|
748
|
-
: currentDiffersFromIncoming && Boolean(changedCurrentVsBaseline) && !mergeApplied;
|
|
749
|
-
const parseFailedConflict = mergePath === "parseFailed";
|
|
750
|
-
const needsSemanticReview = baselineConflict || unresolvedLocalEditsConflict || parseFailedConflict;
|
|
727
|
+
const needsSemanticReview = false;
|
|
751
728
|
reviewRecords.push({
|
|
752
729
|
relPath: rel,
|
|
753
730
|
mergeStrategy: entry.merge_strategy,
|
|
@@ -759,14 +736,6 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
759
736
|
mergeApplied,
|
|
760
737
|
mergePath,
|
|
761
738
|
});
|
|
762
|
-
if (flags.mode === "agent" && needsSemanticReview) {
|
|
763
|
-
reviewSnapshots.set(rel, {
|
|
764
|
-
incomingText: incomingTextOriginal,
|
|
765
|
-
currentText: currentTextForReview,
|
|
766
|
-
baselineText,
|
|
767
|
-
proposedText,
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
739
|
fileContents.set(rel, data);
|
|
771
740
|
if (kind === null)
|
|
772
741
|
additions.push(rel);
|
|
@@ -841,7 +810,7 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
841
810
|
`- .git/**\n\n` +
|
|
842
811
|
`## Notes\n\n` +
|
|
843
812
|
`- The upgrade bundle is validated against framework.manifest.json.\n` +
|
|
844
|
-
`-
|
|
813
|
+
`- The policy gateway file at workspace root is AGENTS.md or CLAUDE.md.\n`;
|
|
845
814
|
const reportMd = `# Upgrade report (${runId})\n\n` +
|
|
846
815
|
`## Actions taken\n\n` +
|
|
847
816
|
`- [ ] Reviewed plan.md\n` +
|
|
@@ -862,30 +831,9 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
862
831
|
},
|
|
863
832
|
files: reviewRecords,
|
|
864
833
|
}, null, 2) + "\n", "utf8");
|
|
865
|
-
if (needsReview.length > 0) {
|
|
866
|
-
const snapshotsRoot = path.join(runDir, "snapshots");
|
|
867
|
-
for (const [rel, snap] of reviewSnapshots.entries()) {
|
|
868
|
-
const variants = [
|
|
869
|
-
["current", snap.currentText],
|
|
870
|
-
["incoming", snap.incomingText],
|
|
871
|
-
["baseline", snap.baselineText],
|
|
872
|
-
["proposed", snap.proposedText],
|
|
873
|
-
];
|
|
874
|
-
for (const [variant, text] of variants) {
|
|
875
|
-
if (text === null)
|
|
876
|
-
continue;
|
|
877
|
-
const outPath = path.join(snapshotsRoot, variant, rel);
|
|
878
|
-
await mkdir(path.dirname(outPath), { recursive: true });
|
|
879
|
-
await writeFile(outPath, text, "utf8");
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
834
|
const relRunDir = path.relative(resolved.gitRoot, runDir);
|
|
884
835
|
process.stdout.write(`Upgrade plan written: ${relRunDir}\n`);
|
|
885
|
-
process.stdout.write(`
|
|
886
|
-
if (needsReview.length > 0) {
|
|
887
|
-
process.stdout.write(`Hint: Create an UPGRADER task and attach ${relRunDir}\n`);
|
|
888
|
-
}
|
|
836
|
+
process.stdout.write(`Review-required files: ${needsReview.length}\n`);
|
|
889
837
|
return 0;
|
|
890
838
|
}
|
|
891
839
|
for (const rel of [...additions, ...updates]) {
|
|
@@ -897,8 +845,8 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
897
845
|
await mkdir(path.dirname(destPath), { recursive: true });
|
|
898
846
|
const data = fileContents.get(rel);
|
|
899
847
|
if (data) {
|
|
900
|
-
if (rel === "AGENTS.md") {
|
|
901
|
-
// If
|
|
848
|
+
if (rel === "AGENTS.md" || rel === "CLAUDE.md") {
|
|
849
|
+
// If policy gateway file is a symlink, avoid overwriting an arbitrary external target.
|
|
902
850
|
// This permits repo-internal symlinks (e.g. the agentplane repo itself) while
|
|
903
851
|
// keeping user workspaces safe.
|
|
904
852
|
try {
|
|
@@ -911,7 +859,7 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
911
859
|
throw new CliError({
|
|
912
860
|
exitCode: exitCodeForError("E_VALIDATION"),
|
|
913
861
|
code: "E_VALIDATION",
|
|
914
|
-
message: `Refusing to overwrite symlinked
|
|
862
|
+
message: `Refusing to overwrite symlinked ${rel} target outside repo: ${linkTarget}. ` +
|
|
915
863
|
"Replace the symlink with a regular file and retry.",
|
|
916
864
|
});
|
|
917
865
|
}
|
|
@@ -933,12 +881,17 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
933
881
|
await writeFile(baselinePath, data);
|
|
934
882
|
}
|
|
935
883
|
}
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
884
|
+
const hasManagedMutations = additions.length > 0 || updates.length > 0;
|
|
885
|
+
const hasSourceMigration = normalizedSourceToPersist !== null;
|
|
886
|
+
const shouldMutateConfig = hasManagedMutations || hasSourceMigration;
|
|
887
|
+
if (shouldMutateConfig) {
|
|
888
|
+
const raw = { ...loaded.raw };
|
|
889
|
+
if (normalizedSourceToPersist) {
|
|
890
|
+
setByDottedKey(raw, "framework.source", normalizedSourceToPersist);
|
|
891
|
+
}
|
|
892
|
+
setByDottedKey(raw, "framework.last_update", new Date().toISOString());
|
|
893
|
+
await saveConfig(resolved.agentplaneDir, raw);
|
|
939
894
|
}
|
|
940
|
-
setByDottedKey(raw, "framework.last_update", new Date().toISOString());
|
|
941
|
-
await saveConfig(resolved.agentplaneDir, raw);
|
|
942
895
|
await writeFile(statePath, JSON.stringify({
|
|
943
896
|
applied_at: new Date().toISOString(),
|
|
944
897
|
source: bundleLayout,
|
|
@@ -952,8 +905,24 @@ export async function cmdUpgradeParsed(opts) {
|
|
|
952
905
|
},
|
|
953
906
|
files: reviewRecords,
|
|
954
907
|
}, null, 2) + "\n", "utf8");
|
|
908
|
+
const commitPaths = [
|
|
909
|
+
...new Set([...additions, ...updates, ...(shouldMutateConfig ? [CONFIG_REL_PATH] : [])]),
|
|
910
|
+
];
|
|
911
|
+
const commit = await createUpgradeCommit({
|
|
912
|
+
gitRoot: resolved.gitRoot,
|
|
913
|
+
paths: commitPaths,
|
|
914
|
+
versionLabel: upgradeVersionLabel,
|
|
915
|
+
source: bundleLayout,
|
|
916
|
+
additions: additions.length,
|
|
917
|
+
updates: updates.length,
|
|
918
|
+
unchanged: skipped.length,
|
|
919
|
+
incidentsAppendedCount,
|
|
920
|
+
});
|
|
955
921
|
await cleanupAutoUpgradeArtifacts({ upgradeStateDir, createdBackups });
|
|
956
922
|
process.stdout.write(`Upgrade applied: ${additions.length} add, ${updates.length} update, ${skipped.length} unchanged\n`);
|
|
923
|
+
if (commit) {
|
|
924
|
+
process.stdout.write(`Upgrade commit: ${commit.hash.slice(0, 12)} ${commit.subject}\n`);
|
|
925
|
+
}
|
|
957
926
|
return 0;
|
|
958
927
|
}
|
|
959
928
|
finally {
|