create-agdf 0.14.2 → 0.14.4
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 +39 -3
- package/generated/.opencode/contracts/control-scaffold.md +17 -2
- package/generated/.opencode/contracts/interaction.md +2 -2
- package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +16 -4
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +6 -4
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/contracts/control-scaffold.md +17 -2
- package/generated/plugins/agdf/meta/contracts/interaction.md +2 -2
- package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
- package/generated/plugins/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +2 -0
- package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +16 -4
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +6 -4
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +7 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
- package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +71 -51
- package/generated/plugins/copilot/agdf/copilot-skills/agdf-delivery-path-search/SKILL.md +16 -4
- package/generated/plugins/copilot/agdf/copilot-skills/agdf-gate-check/SKILL.md +6 -4
- package/generated/plugins/copilot/agdf/copilot-skills/contracts/control-scaffold.md +17 -2
- package/generated/plugins/copilot/agdf/copilot-skills/contracts/interaction.md +2 -2
- package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/plugins/copilot/agdf/plugin.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
- package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/submissions/openai/agdf/meta/contracts/control-scaffold.md +17 -2
- package/generated/submissions/openai/agdf/meta/contracts/interaction.md +2 -2
- package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
- package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/submissions/openai/agdf/skills/delivery-path-search/SKILL.md +16 -4
- package/generated/submissions/openai/agdf/skills/gate-check/SKILL.md +6 -4
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +18 -14
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +7 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
- package/lib/cli/application.js +24 -6
- package/lib/cli/command-registry.js +8 -1
- package/lib/cli/delivery-path-search-command.js +51 -19
- package/lib/cli/parse-args.js +3 -0
- package/lib/control-evaluation/doctor.js +13 -0
- package/lib/control-evaluation/gate-check.js +64 -13
- package/lib/control-evaluation/run-state.js +6 -0
- package/lib/control-state/run-identity.js +9 -0
- package/lib/control-state/run-state-parser.js +4 -2
- package/lib/delivery-path-search/contracts.js +37 -0
- package/lib/delivery-path-search/persistence.js +14 -2
- package/lib/delivery-path-search/search-engine.js +126 -36
- package/lib/delivery-path-search/state-adapter.js +40 -92
- package/lib/installers/claude-cache-recovery.js +76 -0
- package/lib/installers/copilot-settings.js +146 -16
- package/lib/installers/local-marketplace.js +169 -14
- package/lib/installers/plugin-installers.js +96 -9
- package/lib/interaction-presentation.js +57 -7
- package/lib/lifecycle/operations.js +28 -4
- package/lib/lifecycle/result.js +4 -4
- package/lib/release/profile-history.js +222 -0
- package/lib/release/version-bump.js +527 -0
- package/lib/release/version-coherence.js +92 -38
- package/lib/runtime/distribution-profile-history.js +173 -0
- package/lib/runtime/plugin-provenance.js +46 -3
- package/package.json +7 -3
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import {
|
|
4
2
|
CONTRACT_VERSION,
|
|
5
3
|
GENERATOR_CONTRACT_VERSION,
|
|
6
4
|
validateGeneratorRequest,
|
|
7
5
|
} from "./contracts.js";
|
|
8
|
-
import {
|
|
6
|
+
import { evaluateGateCheck } from "../control-evaluation/gate-check.js";
|
|
7
|
+
import { readRunState } from "../control-evaluation/run-state.js";
|
|
9
8
|
|
|
10
9
|
function section(content, heading) {
|
|
11
10
|
return (
|
|
@@ -15,100 +14,44 @@ function section(content, heading) {
|
|
|
15
14
|
);
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.split("|")
|
|
23
|
-
.slice(1, -1)
|
|
24
|
-
.map((cell) => cell.trim());
|
|
25
|
-
if (
|
|
26
|
-
cells.length >= 2 &&
|
|
27
|
-
cells[0] &&
|
|
28
|
-
!cells[0].startsWith("---") &&
|
|
29
|
-
cells[0] !== "Question"
|
|
30
|
-
)
|
|
31
|
-
answers[cells[0]] = cells[1];
|
|
32
|
-
}
|
|
33
|
-
return answers;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function statusCard(content) {
|
|
37
|
-
const card = {};
|
|
38
|
-
for (const line of section(content, "Run Status Card").split(/\r?\n/)) {
|
|
39
|
-
const cells = line
|
|
40
|
-
.split("|")
|
|
41
|
-
.slice(1, -1)
|
|
42
|
-
.map((cell) => cell.trim().replace(/^`|`$/g, ""));
|
|
43
|
-
if (
|
|
44
|
-
cells.length >= 2 &&
|
|
45
|
-
cells[0] &&
|
|
46
|
-
!cells[0].startsWith("---") &&
|
|
47
|
-
cells[0] !== "Run status"
|
|
48
|
-
)
|
|
49
|
-
card[cells[0]] = cells[1];
|
|
50
|
-
}
|
|
51
|
-
return card;
|
|
17
|
+
function metaField(content, name) {
|
|
18
|
+
return content.match(new RegExp(`^- ${name}:\\s*(.+)$`, "m"))?.[1]
|
|
19
|
+
?.replace(/^`|`$/g, "")
|
|
20
|
+
.trim() ?? "";
|
|
52
21
|
}
|
|
53
22
|
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
23
|
+
export function searchInputFromControl(targetDir, options = {}, dependencies = {}) {
|
|
24
|
+
const evaluate = dependencies.evaluateGateCheck ?? evaluateGateCheck;
|
|
25
|
+
const readRun = dependencies.readRunState ?? readRunState;
|
|
26
|
+
const selection = options.scopeKey ? { runId: options.scopeKey } : {};
|
|
27
|
+
const gate = evaluate(targetDir, selection);
|
|
28
|
+
const run = readRun(targetDir, selection);
|
|
29
|
+
const content = run.content ?? "";
|
|
30
|
+
const evaluatedRunId = gate.status_card?.run_id ?? "";
|
|
31
|
+
const evaluatedRevision = gate.status_presentation?.revision_id ?? "";
|
|
32
|
+
const runId = metaField(content, "run_id");
|
|
33
|
+
const runRevision = metaField(content, "revision_id");
|
|
34
|
+
const runGate = metaField(content, "current_gate");
|
|
35
|
+
let inputFailureCode = "";
|
|
36
|
+
if (!content || run.resolution_error) inputFailureCode = "canonical_control_unavailable";
|
|
37
|
+
else if (!evaluatedRunId || !evaluatedRevision) inputFailureCode = "canonical_snapshot_identity_missing";
|
|
38
|
+
else if (evaluatedRunId !== runId || evaluatedRevision !== runRevision || gate.current_gate !== runGate) {
|
|
39
|
+
inputFailureCode = "stale_control_snapshot";
|
|
40
|
+
} else if (!Array.isArray(gate.allowed) || gate.allowed.length === 0) {
|
|
41
|
+
inputFailureCode = "canonical_actions_unavailable";
|
|
68
42
|
}
|
|
69
|
-
const resolved = existsSync(canonicalRoot)
|
|
70
|
-
? resolveRuns(targetDir, {
|
|
71
|
-
runIdArg: options.scopeKey,
|
|
72
|
-
runIdEnv: process.env.AGDF_RUN_ID,
|
|
73
|
-
})
|
|
74
|
-
: null;
|
|
75
|
-
const runPath =
|
|
76
|
-
resolved?.run.path ?? join(targetDir, ".agdf", "control", "AGDF_RUN.md");
|
|
77
|
-
if (!existsSync(runPath)) throw new Error("missing AGDF run state");
|
|
78
|
-
const content = readFileSync(runPath, "utf8");
|
|
79
|
-
const meta = Object.fromEntries(
|
|
80
|
-
[...content.matchAll(/^- ([a-z_]+):\s*(.+)$/gm)].map((match) => [
|
|
81
|
-
match[1],
|
|
82
|
-
match[2].replace(/^`|`$/g, "").trim(),
|
|
83
|
-
]),
|
|
84
|
-
);
|
|
85
|
-
const answers = tableAnswers(content);
|
|
86
|
-
const card = statusCard(content);
|
|
87
43
|
const result = {
|
|
88
44
|
contract_version: CONTRACT_VERSION,
|
|
89
|
-
scope_key: options.scopeKey
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
),
|
|
100
|
-
]
|
|
101
|
-
.slice(1)
|
|
102
|
-
.map((match) => `${match[1].trim()}: ${match[2].trim()}`),
|
|
103
|
-
risks: section(content, "Risks")
|
|
104
|
-
.split(/\r?\n/)
|
|
105
|
-
.filter(
|
|
106
|
-
(line) =>
|
|
107
|
-
line.startsWith("|") &&
|
|
108
|
-
!line.includes("---") &&
|
|
109
|
-
!line.includes("| Risk |"),
|
|
110
|
-
)
|
|
111
|
-
.map((line) => line.split("|")[1].trim()),
|
|
45
|
+
scope_key: evaluatedRunId || runId || options.scopeKey || "unknown-scope",
|
|
46
|
+
scope_revision: evaluatedRevision || runRevision || "unversioned",
|
|
47
|
+
objective: section(content, "Objective").trim() || "Canonical Delivery Path Search input unavailable",
|
|
48
|
+
current_gate: gate.current_gate || runGate || "unknown",
|
|
49
|
+
allowed_actions: inputFailureCode ? [] : [...gate.allowed],
|
|
50
|
+
forbidden_actions: Array.isArray(gate.forbidden) ? [...gate.forbidden] : [],
|
|
51
|
+
evidence_refs: Array.isArray(gate.evidence_refs)
|
|
52
|
+
? gate.evidence_refs.map((item) => `${item.evidence}: ${item.source}`)
|
|
53
|
+
: [],
|
|
54
|
+
risks: Array.isArray(run.risks) ? run.risks.map((item) => item.risk).filter(Boolean) : [],
|
|
112
55
|
enforcement: options.enforcement ?? {
|
|
113
56
|
level: "instruction_only",
|
|
114
57
|
evidence: ["surface instruction prohibits implementation during search"],
|
|
@@ -122,6 +65,11 @@ export function searchInputFromControl(targetDir, options = {}) {
|
|
|
122
65
|
stability_window: options.stabilityWindow ?? 3,
|
|
123
66
|
},
|
|
124
67
|
};
|
|
68
|
+
if (inputFailureCode) {
|
|
69
|
+
result.input_failure_code = inputFailureCode;
|
|
70
|
+
result.input_failure_detail = run.resolution_error || (gate.blocking_reason !== "none" ? gate.blocking_reason : "") || "canonical input is not ready";
|
|
71
|
+
result.input_recovery_action = gate.next_allowed_action || "Run canonical AGDF gate-check and repair the selected control state.";
|
|
72
|
+
}
|
|
125
73
|
if (options.generation?.enabled)
|
|
126
74
|
result.generation = {
|
|
127
75
|
enabled: true,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, lstatSync, realpathSync, rmSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { win32 } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
function result(status, reason, removedPath = null, evidence = []) {
|
|
7
|
+
return Object.freeze({ status, reason, removed_path: removedPath, evidence });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalized(path) {
|
|
11
|
+
return win32.resolve(path).toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseRenameOperands(text) {
|
|
15
|
+
const matches = [...text.matchAll(/\brename\s+['"]([^'"]+)['"]\s*->\s*['"]([^'"]+)['"]/gi)];
|
|
16
|
+
return matches.length === 1 ? matches[0].slice(1, 3) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function recoverClaudeCacheTemp({
|
|
20
|
+
error,
|
|
21
|
+
expectedVersion,
|
|
22
|
+
platform = process.platform,
|
|
23
|
+
env = process.env,
|
|
24
|
+
home = homedir(),
|
|
25
|
+
fs = { existsSync, lstatSync, realpathSync, rmSync },
|
|
26
|
+
} = {}) {
|
|
27
|
+
const text = String(error?.message ?? error ?? "");
|
|
28
|
+
if (platform !== "win32" || !/\bEPERM\b/i.test(text) || !/\brename\b/i.test(text)) {
|
|
29
|
+
return result("not_applicable", "claude_cache_temp_recovery_not_applicable");
|
|
30
|
+
}
|
|
31
|
+
const operands = parseRenameOperands(text);
|
|
32
|
+
if (!operands || typeof expectedVersion !== "string" || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(expectedVersion)) {
|
|
33
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
34
|
+
}
|
|
35
|
+
const configRoot = win32.resolve(env.CLAUDE_CONFIG_DIR || win32.join(home, ".claude"));
|
|
36
|
+
const namespace = win32.join(configRoot, "plugins", "cache", "agdf", "agdf");
|
|
37
|
+
const absoluteOperands = operands.filter((path) => win32.isAbsolute(path));
|
|
38
|
+
if (absoluteOperands.length !== 2
|
|
39
|
+
|| absoluteOperands.some((path) => normalized(win32.dirname(path)) !== normalized(namespace))) {
|
|
40
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
41
|
+
}
|
|
42
|
+
const [candidate, destination] = absoluteOperands;
|
|
43
|
+
if (!/^temp_local_[0-9A-Za-z._-]+$/.test(win32.basename(candidate))
|
|
44
|
+
|| !fs.existsSync(candidate)) {
|
|
45
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
46
|
+
}
|
|
47
|
+
if (win32.basename(destination) !== expectedVersion || fs.existsSync(destination)) {
|
|
48
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
49
|
+
}
|
|
50
|
+
let candidateStats;
|
|
51
|
+
let realNamespace;
|
|
52
|
+
let realCandidate;
|
|
53
|
+
try {
|
|
54
|
+
candidateStats = fs.lstatSync(candidate);
|
|
55
|
+
realNamespace = fs.realpathSync(namespace);
|
|
56
|
+
realCandidate = fs.realpathSync(candidate);
|
|
57
|
+
} catch {
|
|
58
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
59
|
+
}
|
|
60
|
+
if (!candidateStats.isDirectory() || candidateStats.isSymbolicLink()
|
|
61
|
+
|| normalized(win32.dirname(realCandidate)) !== normalized(realNamespace)
|
|
62
|
+
|| normalized(realCandidate) !== normalized(candidate)) {
|
|
63
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
fs.rmSync(candidate, { recursive: true, force: false });
|
|
67
|
+
} catch {
|
|
68
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
69
|
+
}
|
|
70
|
+
return result(
|
|
71
|
+
"recovered",
|
|
72
|
+
"claude_cache_temp_recovery_bounded_retry",
|
|
73
|
+
candidate,
|
|
74
|
+
[`cache_entry:${win32.basename(candidate)}`, `target_version:${expectedVersion}`],
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -1,49 +1,179 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
5
6
|
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
6
7
|
|
|
8
|
+
const COPILOT_PLUGIN_SELECTOR = "agdf@agdf";
|
|
9
|
+
const COPILOT_LOCAL_SETTINGS = join(".github", "copilot", "settings.local.json");
|
|
10
|
+
const COPILOT_SHARED_SETTINGS = join(".github", "copilot", "settings.json");
|
|
11
|
+
|
|
7
12
|
export function defaultCopilotSettingsPath({ env = process.env, home = homedir() } = {}) {
|
|
8
13
|
return join(resolve(env.COPILOT_HOME || join(home, ".copilot")), "settings.json");
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export function readCopilotSettings(path = defaultCopilotSettingsPath()) {
|
|
12
|
-
if (!existsSync(path)) return { exists: false, settings: {} };
|
|
17
|
+
if (!existsSync(path)) return { exists: false, settings: {}, content: null };
|
|
13
18
|
const stats = lstatSync(path);
|
|
14
19
|
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("AGDF_COPILOT_SETTINGS_UNOWNED_PATH");
|
|
15
20
|
try {
|
|
16
|
-
const
|
|
21
|
+
const content = readFileSync(path, "utf8");
|
|
22
|
+
const settings = JSON.parse(content);
|
|
17
23
|
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("not an object");
|
|
18
24
|
if (settings.enabledPlugins !== undefined
|
|
19
25
|
&& (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object" || Array.isArray(settings.enabledPlugins))) {
|
|
20
26
|
throw new Error("enabledPlugins must be an object");
|
|
21
27
|
}
|
|
22
|
-
return { exists: true, settings };
|
|
28
|
+
return { exists: true, settings, content };
|
|
23
29
|
} catch (error) {
|
|
24
30
|
throw new Error(`AGDF_COPILOT_SETTINGS_INVALID: ${error.message}`);
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
function
|
|
29
|
-
|
|
34
|
+
function atomicContentWrite(path, content, dependencies = {}) {
|
|
35
|
+
const makeDirectory = dependencies.mkdir ?? mkdirSync;
|
|
36
|
+
const write = dependencies.write ?? writeFileSync;
|
|
37
|
+
const rename = dependencies.rename ?? renameSyncWithRetry;
|
|
38
|
+
const remove = dependencies.remove ?? rmSync;
|
|
39
|
+
const changeMode = dependencies.chmod ?? chmodSync;
|
|
40
|
+
const exists = dependencies.exists ?? existsSync;
|
|
41
|
+
const stat = dependencies.lstat ?? lstatSync;
|
|
42
|
+
makeDirectory(dirname(path), { recursive: true });
|
|
30
43
|
const temp = `${path}.agdf-tmp-${process.pid}`;
|
|
31
44
|
const backup = `${path}.agdf-backup-${process.pid}`;
|
|
32
|
-
const hadPrevious =
|
|
33
|
-
const mode = hadPrevious ?
|
|
34
|
-
|
|
35
|
-
if (process.platform !== "win32")
|
|
45
|
+
const hadPrevious = exists(path);
|
|
46
|
+
const mode = hadPrevious ? stat(path).mode & 0o777 : 0o600;
|
|
47
|
+
write(temp, content, { encoding: "utf8", flag: "wx", mode });
|
|
48
|
+
if (process.platform !== "win32") changeMode(temp, mode);
|
|
36
49
|
try {
|
|
37
|
-
if (hadPrevious && process.platform === "win32")
|
|
38
|
-
|
|
39
|
-
if (hadPrevious && process.platform === "win32")
|
|
50
|
+
if (hadPrevious && process.platform === "win32") rename(path, backup);
|
|
51
|
+
rename(temp, path);
|
|
52
|
+
if (hadPrevious && process.platform === "win32") remove(backup, { force: true });
|
|
40
53
|
} catch (error) {
|
|
41
|
-
try {
|
|
42
|
-
if (hadPrevious && process.platform === "win32" &&
|
|
54
|
+
try { remove(temp, { force: true }); } catch {}
|
|
55
|
+
if (hadPrevious && process.platform === "win32" && exists(backup) && !exists(path)) rename(backup, path);
|
|
43
56
|
throw error;
|
|
44
57
|
}
|
|
45
58
|
}
|
|
46
59
|
|
|
60
|
+
export function atomicSettingsWrite(path, settings, dependencies = {}) {
|
|
61
|
+
atomicContentWrite(path, `${JSON.stringify(settings, null, 2)}\n`, dependencies);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function repositoryCopilotSettingsPath(targetDir, { shared = false } = {}) {
|
|
65
|
+
const root = resolve(targetDir);
|
|
66
|
+
if (!existsSync(root) || lstatSync(root).isSymbolicLink() || !lstatSync(root).isDirectory()) {
|
|
67
|
+
throw new Error(`AGDF_COPILOT_REPOSITORY_UNOWNED_PATH: ${root}`);
|
|
68
|
+
}
|
|
69
|
+
const path = resolve(root, shared ? COPILOT_SHARED_SETTINGS : COPILOT_LOCAL_SETTINGS);
|
|
70
|
+
const fromRoot = relative(root, path);
|
|
71
|
+
if (!fromRoot || fromRoot.startsWith("..") || isAbsolute(fromRoot)) {
|
|
72
|
+
throw new Error("AGDF_COPILOT_REPOSITORY_SETTINGS_OUTSIDE_ROOT");
|
|
73
|
+
}
|
|
74
|
+
for (const candidate of [root, join(root, ".github"), join(root, ".github", "copilot")]) {
|
|
75
|
+
if (existsSync(candidate) && lstatSync(candidate).isSymbolicLink()) {
|
|
76
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_UNOWNED_PATH: ${candidate}`);
|
|
77
|
+
}
|
|
78
|
+
if (existsSync(candidate) && !lstatSync(candidate).isDirectory()) {
|
|
79
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_UNOWNED_PATH: ${candidate}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (existsSync(path) && (lstatSync(path).isSymbolicLink() || !lstatSync(path).isFile())) {
|
|
83
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_UNOWNED_PATH: ${path}`);
|
|
84
|
+
}
|
|
85
|
+
return path;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function assertCopilotLocalSettingsIgnored(targetDir, { exec = execFileSync } = {}) {
|
|
89
|
+
const root = resolve(targetDir);
|
|
90
|
+
try {
|
|
91
|
+
exec("git", ["check-ignore", "--quiet", "--", COPILOT_LOCAL_SETTINGS], {
|
|
92
|
+
cwd: root,
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error?.code === "ENOENT") {
|
|
98
|
+
throw new Error("AGDF_COPILOT_GIT_UNAVAILABLE: install Git and retry personal disable, or use --shared deliberately.");
|
|
99
|
+
}
|
|
100
|
+
if (error?.status === 1) {
|
|
101
|
+
throw new Error(`AGDF_COPILOT_LOCAL_SETTINGS_NOT_IGNORED: add ${COPILOT_LOCAL_SETTINGS} to an effective Git ignore source and retry, or use --shared deliberately.`);
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`AGDF_COPILOT_GIT_IGNORE_UNVERIFIED: ${error?.message || "Git could not verify the repository ignore state."}`);
|
|
104
|
+
}
|
|
105
|
+
return { status: "ignored", path: COPILOT_LOCAL_SETTINGS };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function planCopilotRepositoryDisable({ targetDir, shared = false, exec = execFileSync }) {
|
|
109
|
+
const root = resolve(targetDir);
|
|
110
|
+
const path = repositoryCopilotSettingsPath(root, { shared });
|
|
111
|
+
if (!shared) assertCopilotLocalSettingsIgnored(root, { exec });
|
|
112
|
+
const before = readCopilotSettings(path);
|
|
113
|
+
const current = before.settings.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR];
|
|
114
|
+
if (current !== undefined && typeof current !== "boolean") {
|
|
115
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_AMBIGUOUS_PLUGIN_STATE: ${path}`);
|
|
116
|
+
}
|
|
117
|
+
const next = structuredClone(before.settings);
|
|
118
|
+
next.enabledPlugins = { ...(next.enabledPlugins ?? {}), [COPILOT_PLUGIN_SELECTOR]: false };
|
|
119
|
+
return Object.freeze({
|
|
120
|
+
path,
|
|
121
|
+
root,
|
|
122
|
+
shared,
|
|
123
|
+
audience: shared ? "shared" : "personal",
|
|
124
|
+
selector: COPILOT_PLUGIN_SELECTOR,
|
|
125
|
+
before: Object.freeze({ exists: before.exists, settings: structuredClone(before.settings), content: before.content }),
|
|
126
|
+
settings: Object.freeze(next),
|
|
127
|
+
changed: current !== false,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function applyCopilotRepositoryDisable(mutation, dependencies = {}) {
|
|
132
|
+
if (!mutation?.path || mutation.selector !== COPILOT_PLUGIN_SELECTOR || mutation.settings?.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR] !== false) {
|
|
133
|
+
throw new Error("AGDF_COPILOT_SETTINGS_MUTATION_INVALID");
|
|
134
|
+
}
|
|
135
|
+
const read = dependencies.read ?? readCopilotSettings;
|
|
136
|
+
const write = dependencies.writeSettings ?? ((path, settings) => atomicSettingsWrite(path, settings, dependencies));
|
|
137
|
+
const writeContent = dependencies.writeContent ?? ((path, content) => atomicContentWrite(path, content, dependencies));
|
|
138
|
+
const remove = dependencies.remove ?? rmSync;
|
|
139
|
+
const resolvedPath = repositoryCopilotSettingsPath(mutation.root, { shared: mutation.shared });
|
|
140
|
+
if (resolvedPath !== mutation.path) throw new Error("AGDF_COPILOT_SETTINGS_MUTATION_PATH_MISMATCH");
|
|
141
|
+
const current = read(mutation.path);
|
|
142
|
+
if (current.exists !== mutation.before.exists || (current.exists && current.content !== mutation.before.content)) {
|
|
143
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_CHANGED_DURING_APPLY: ${mutation.path}`);
|
|
144
|
+
}
|
|
145
|
+
if (!mutation.changed) return { status: "unchanged", path: mutation.path };
|
|
146
|
+
try {
|
|
147
|
+
write(mutation.path, mutation.settings);
|
|
148
|
+
const verified = read(mutation.path);
|
|
149
|
+
if (verified.settings.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR] !== false) {
|
|
150
|
+
throw new Error("AGDF_COPILOT_SETTINGS_VERIFICATION_FAILED");
|
|
151
|
+
}
|
|
152
|
+
return { status: "written", path: mutation.path };
|
|
153
|
+
} catch (error) {
|
|
154
|
+
try {
|
|
155
|
+
if (mutation.before.exists) writeContent(mutation.path, mutation.before.content);
|
|
156
|
+
else remove(mutation.path, { force: true });
|
|
157
|
+
} catch (rollbackError) {
|
|
158
|
+
error.rollbackError = rollbackError;
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function verifyCopilotRepositoryDisabled(targetDir, { shared = false } = {}) {
|
|
165
|
+
try {
|
|
166
|
+
const path = repositoryCopilotSettingsPath(targetDir, { shared });
|
|
167
|
+
if (!existsSync(path)) return { status: "failed", evidence: [`missing:${path}`] };
|
|
168
|
+
const state = readCopilotSettings(path);
|
|
169
|
+
return state.settings.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR] === false
|
|
170
|
+
? { status: "healthy", evidence: [`${path}:${COPILOT_PLUGIN_SELECTOR}:enabled=false`, `audience:${shared ? "shared" : "personal"}`] }
|
|
171
|
+
: { status: "failed", evidence: [`postcondition_failed:${path}:${COPILOT_PLUGIN_SELECTOR}`] };
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return { status: "failed", evidence: [`verification_failed:${resolve(targetDir)}:${error.message}`] };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
47
177
|
export function configureCopilotDeclarativePlugin({ path = defaultCopilotSettingsPath(), pluginRoot }) {
|
|
48
178
|
if (typeof pluginRoot !== "string" || !pluginRoot.trim() || !isAbsolute(pluginRoot)) {
|
|
49
179
|
throw new Error("AGDF_COPILOT_PLUGIN_SPEC_INVALID");
|
|
@@ -64,7 +194,7 @@ export function configureCopilotDeclarativePlugin({ path = defaultCopilotSetting
|
|
|
64
194
|
path,
|
|
65
195
|
spec,
|
|
66
196
|
rollback() {
|
|
67
|
-
if (before.exists)
|
|
197
|
+
if (before.exists) atomicContentWrite(path, before.content);
|
|
68
198
|
else rmSync(path, { force: true });
|
|
69
199
|
},
|
|
70
200
|
};
|