@imdeadpool/guardex 7.0.22 → 7.0.24
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 +11 -0
- package/package.json +5 -1
- package/src/cli/args.js +89 -0
- package/src/cli/main.js +54 -75
- package/src/context.js +16 -2
- package/src/core/stdin.js +52 -0
- package/src/core/versions.js +33 -0
- package/src/hooks/index.js +64 -0
- package/src/output/index.js +64 -4
- package/src/report/session-severity.js +282 -0
- package/src/scaffold/index.js +78 -131
- package/src/toolchain/index.js +6 -70
- package/templates/AGENTS.multiagent-safety.md +25 -0
- package/templates/scripts/agent-branch-finish.sh +79 -1
- package/templates/scripts/agent-branch-start.sh +35 -0
- package/templates/scripts/agent-session-state.js +62 -1
- package/templates/scripts/codex-agent.sh +38 -0
- package/templates/scripts/install-vscode-active-agents-extension.js +38 -11
- package/templates/scripts/openspec/init-plan-workspace.sh +34 -3
- package/templates/vscode/guardex-active-agents/README.md +7 -6
- package/templates/vscode/guardex-active-agents/extension.js +523 -73
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/package.json +13 -3
- package/templates/vscode/guardex-active-agents/session-schema.js +311 -4
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
const TASK_SIZE_UPPER_BOUNDS = {
|
|
2
|
+
'narrow-patch': 1_800_000,
|
|
3
|
+
'medium-change': 4_000_000,
|
|
4
|
+
'large-change': 8_000_000,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const TASK_SIZE_VALUES = Object.keys(TASK_SIZE_UPPER_BOUNDS);
|
|
8
|
+
const TASK_SIZE_SET = new Set(TASK_SIZE_VALUES);
|
|
9
|
+
const FRAGMENTATION_PRESET_SCORES = {
|
|
10
|
+
clean: 0,
|
|
11
|
+
'few-extra-checks': 5,
|
|
12
|
+
'repeated-follow-ups': 10,
|
|
13
|
+
looping: 18,
|
|
14
|
+
'dominant-loop': 25,
|
|
15
|
+
};
|
|
16
|
+
const FINISH_PATH_PRESET_SCORES = {
|
|
17
|
+
'clear-early': 0,
|
|
18
|
+
'minor-hesitation': 5,
|
|
19
|
+
'late-decision': 10,
|
|
20
|
+
reopening: 15,
|
|
21
|
+
};
|
|
22
|
+
const POST_PROOF_PRESET_SCORES = {
|
|
23
|
+
'stops-soon': 0,
|
|
24
|
+
'small-tail': 5,
|
|
25
|
+
'notable-tail': 10,
|
|
26
|
+
'heavy-tail': 15,
|
|
27
|
+
};
|
|
28
|
+
const DRIVER_TIE_BREAK = ['fragmentation', 'writeStdin', 'finishPath', 'postProof', 'cost'];
|
|
29
|
+
const DRIVER_LABELS = {
|
|
30
|
+
cost: 'cost vs expected scope',
|
|
31
|
+
fragmentation: 'turn fragmentation',
|
|
32
|
+
writeStdin: 'write_stdin churn',
|
|
33
|
+
finishPath: 'finish-path discipline',
|
|
34
|
+
postProof: 'post-proof drift',
|
|
35
|
+
};
|
|
36
|
+
const LABEL_BANDS = [
|
|
37
|
+
{ max: 15, label: 'Healthy' },
|
|
38
|
+
{ max: 30, label: 'Mildly fragmented' },
|
|
39
|
+
{ max: 50, label: 'Inefficient' },
|
|
40
|
+
{ max: 75, label: 'Runaway' },
|
|
41
|
+
{ max: 100, label: 'Catastrophic' },
|
|
42
|
+
];
|
|
43
|
+
const SESSION_SEVERITY_SUBCOMMAND = 'session-severity';
|
|
44
|
+
const SESSION_SEVERITY_USAGE_ARGS = [
|
|
45
|
+
`--task-size <${TASK_SIZE_VALUES.join('|')}>`,
|
|
46
|
+
'--tokens <count>',
|
|
47
|
+
'--exec-count <count>',
|
|
48
|
+
'--write-stdin-count <count>',
|
|
49
|
+
'--completion-before-tail <yes|no>',
|
|
50
|
+
'[--expected-bound <count>]',
|
|
51
|
+
`[--fragmentation <${Object.keys(FRAGMENTATION_PRESET_SCORES).join('|')}|0-25>]`,
|
|
52
|
+
`[--finish-path <${Object.keys(FINISH_PATH_PRESET_SCORES).join('|')}|0-15>]`,
|
|
53
|
+
`[--post-proof <${Object.keys(POST_PROOF_PRESET_SCORES).join('|')}|0-15>]`,
|
|
54
|
+
'[--json]',
|
|
55
|
+
].join(' ');
|
|
56
|
+
const SESSION_SEVERITY_COMMAND_TAIL = `${SESSION_SEVERITY_SUBCOMMAND} ${SESSION_SEVERITY_USAGE_ARGS}`;
|
|
57
|
+
const SESSION_SEVERITY_EXAMPLE_ARGS = [
|
|
58
|
+
'--task-size',
|
|
59
|
+
'narrow-patch',
|
|
60
|
+
'--tokens',
|
|
61
|
+
'3850000',
|
|
62
|
+
'--exec-count',
|
|
63
|
+
'18',
|
|
64
|
+
'--write-stdin-count',
|
|
65
|
+
'6',
|
|
66
|
+
'--completion-before-tail',
|
|
67
|
+
'yes',
|
|
68
|
+
'--fragmentation',
|
|
69
|
+
'14',
|
|
70
|
+
'--finish-path',
|
|
71
|
+
'6',
|
|
72
|
+
'--post-proof',
|
|
73
|
+
'4',
|
|
74
|
+
];
|
|
75
|
+
const SESSION_SEVERITY_EXAMPLE_TAIL = `${SESSION_SEVERITY_SUBCOMMAND} ${SESSION_SEVERITY_EXAMPLE_ARGS.join(' ')}`;
|
|
76
|
+
|
|
77
|
+
function formatInteger(value) {
|
|
78
|
+
return Number(value).toLocaleString('en-US');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseRequiredPositiveInteger(name, rawValue, { allowZero = true } = {}) {
|
|
82
|
+
const parsed = Number.parseInt(String(rawValue || ''), 10);
|
|
83
|
+
if (!Number.isFinite(parsed) || (!allowZero && parsed <= 0) || (allowZero && parsed < 0)) {
|
|
84
|
+
throw new Error(`${name} requires ${allowZero ? 'a non-negative integer' : 'a positive integer'} value`);
|
|
85
|
+
}
|
|
86
|
+
return parsed;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseBooleanFlag(name, rawValue) {
|
|
90
|
+
const normalized = String(rawValue || '').trim().toLowerCase();
|
|
91
|
+
if (normalized === 'yes' || normalized === 'true' || normalized === '1') {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (normalized === 'no' || normalized === 'false' || normalized === '0') {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`${name} requires yes/no (or true/false, 1/0)`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function clampScore(value, min, max) {
|
|
101
|
+
return Math.max(min, Math.min(max, Math.round(value)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseTaskSize(rawTaskSize) {
|
|
105
|
+
const normalized = String(rawTaskSize || '').trim();
|
|
106
|
+
if (!TASK_SIZE_SET.has(normalized)) {
|
|
107
|
+
throw new Error(`--task-size must be one of: ${TASK_SIZE_VALUES.join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveExpectedUpperBound(taskSize, rawExpectedBound) {
|
|
113
|
+
if (rawExpectedBound) {
|
|
114
|
+
return parseRequiredPositiveInteger('--expected-bound', rawExpectedBound, { allowZero: false });
|
|
115
|
+
}
|
|
116
|
+
return TASK_SIZE_UPPER_BOUNDS[taskSize];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function scoreCost(tokens, expectedUpperBound) {
|
|
120
|
+
const ratio = tokens / expectedUpperBound;
|
|
121
|
+
if (ratio <= 1.0) return 0;
|
|
122
|
+
if (ratio <= 1.5) return 5;
|
|
123
|
+
if (ratio <= 2.5) return 10;
|
|
124
|
+
if (ratio <= 4.0) return 18;
|
|
125
|
+
if (ratio <= 6.0) return 24;
|
|
126
|
+
return 30;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function scoreFragmentation(execCount, override) {
|
|
130
|
+
if (override) {
|
|
131
|
+
if (Object.prototype.hasOwnProperty.call(FRAGMENTATION_PRESET_SCORES, override)) {
|
|
132
|
+
return FRAGMENTATION_PRESET_SCORES[override];
|
|
133
|
+
}
|
|
134
|
+
return clampScore(parseRequiredPositiveInteger('--fragmentation', override), 0, 25);
|
|
135
|
+
}
|
|
136
|
+
if (execCount <= 4) return 0;
|
|
137
|
+
if (execCount <= 8) return 5;
|
|
138
|
+
if (execCount <= 16) return 10;
|
|
139
|
+
if (execCount <= 28) return 18;
|
|
140
|
+
return 25;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function scoreWriteStdin(writeStdinCount) {
|
|
144
|
+
if (writeStdinCount <= 0) return 0;
|
|
145
|
+
if (writeStdinCount <= 3) return 5;
|
|
146
|
+
if (writeStdinCount <= 6) return 10;
|
|
147
|
+
return 15;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function scoreFinishPath(completionBeforeTail, override) {
|
|
151
|
+
if (override) {
|
|
152
|
+
if (Object.prototype.hasOwnProperty.call(FINISH_PATH_PRESET_SCORES, override)) {
|
|
153
|
+
return FINISH_PATH_PRESET_SCORES[override];
|
|
154
|
+
}
|
|
155
|
+
return clampScore(parseRequiredPositiveInteger('--finish-path', override), 0, 15);
|
|
156
|
+
}
|
|
157
|
+
return completionBeforeTail ? 0 : 5;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function scorePostProof(completionBeforeTail, override) {
|
|
161
|
+
if (override) {
|
|
162
|
+
if (Object.prototype.hasOwnProperty.call(POST_PROOF_PRESET_SCORES, override)) {
|
|
163
|
+
return POST_PROOF_PRESET_SCORES[override];
|
|
164
|
+
}
|
|
165
|
+
return clampScore(parseRequiredPositiveInteger('--post-proof', override), 0, 15);
|
|
166
|
+
}
|
|
167
|
+
return completionBeforeTail ? 0 : 10;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function labelForTotal(total) {
|
|
171
|
+
return LABEL_BANDS.find((band) => total <= band.max)?.label || LABEL_BANDS[LABEL_BANDS.length - 1].label;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function buildSessionSeverityReport(options) {
|
|
175
|
+
const taskSize = parseTaskSize(options.taskSize);
|
|
176
|
+
const tokens = parseRequiredPositiveInteger('--tokens', options.tokens);
|
|
177
|
+
const execCount = parseRequiredPositiveInteger('--exec-count', options.execCount);
|
|
178
|
+
const writeStdinCount = parseRequiredPositiveInteger('--write-stdin-count', options.writeStdinCount);
|
|
179
|
+
const completionBeforeTail = parseBooleanFlag('--completion-before-tail', options.completionBeforeTail);
|
|
180
|
+
const expectedUpperBound = resolveExpectedUpperBound(taskSize, options.expectedBound);
|
|
181
|
+
const costRatio = tokens / expectedUpperBound;
|
|
182
|
+
const scores = {
|
|
183
|
+
cost: scoreCost(tokens, expectedUpperBound),
|
|
184
|
+
fragmentation: scoreFragmentation(execCount, options.fragmentation),
|
|
185
|
+
writeStdin: scoreWriteStdin(writeStdinCount),
|
|
186
|
+
finishPath: scoreFinishPath(completionBeforeTail, options.finishPath),
|
|
187
|
+
postProof: scorePostProof(completionBeforeTail, options.postProof),
|
|
188
|
+
};
|
|
189
|
+
const total = scores.cost + scores.fragmentation + scores.writeStdin + scores.finishPath + scores.postProof;
|
|
190
|
+
const label = labelForTotal(total);
|
|
191
|
+
const rankedDimensions = Object.entries(scores)
|
|
192
|
+
.map(([key, score]) => ({ key, score, label: DRIVER_LABELS[key] }))
|
|
193
|
+
.filter((entry) => entry.score > 0)
|
|
194
|
+
.sort((left, right) => {
|
|
195
|
+
if (right.score !== left.score) {
|
|
196
|
+
return right.score - left.score;
|
|
197
|
+
}
|
|
198
|
+
return DRIVER_TIE_BREAK.indexOf(left.key) - DRIVER_TIE_BREAK.indexOf(right.key);
|
|
199
|
+
});
|
|
200
|
+
const primaryDriver = rankedDimensions[0] ? rankedDimensions[0].label : 'none';
|
|
201
|
+
const secondaries = rankedDimensions.slice(1).map((entry) => entry.label);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
taskSize,
|
|
205
|
+
expectedUpperBound,
|
|
206
|
+
tokens,
|
|
207
|
+
execCount,
|
|
208
|
+
writeStdinCount,
|
|
209
|
+
completionBeforeTail,
|
|
210
|
+
costRatio,
|
|
211
|
+
scores: {
|
|
212
|
+
...scores,
|
|
213
|
+
total,
|
|
214
|
+
},
|
|
215
|
+
label,
|
|
216
|
+
primaryDriver,
|
|
217
|
+
secondaries,
|
|
218
|
+
outputLine: `Score ${total}/100 — ${label}. Primary: ${primaryDriver}. Secondaries: ${
|
|
219
|
+
secondaries.length > 0 ? secondaries.join(', ') : 'none'
|
|
220
|
+
}.`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function renderSessionSeverityReport(report) {
|
|
225
|
+
return [
|
|
226
|
+
report.outputLine,
|
|
227
|
+
'',
|
|
228
|
+
`Task size: ${report.taskSize}`,
|
|
229
|
+
`Expected upper bound: ${report.expectedUpperBound}`,
|
|
230
|
+
`Actual tokens: ${report.tokens}`,
|
|
231
|
+
`Exec count: ${report.execCount}`,
|
|
232
|
+
`write_stdin count: ${report.writeStdinCount}`,
|
|
233
|
+
`Completion before tail churn: ${report.completionBeforeTail ? 'yes' : 'no'}`,
|
|
234
|
+
`Cost ratio: ${report.costRatio.toFixed(2)}x`,
|
|
235
|
+
'',
|
|
236
|
+
`A. Cost vs expected scope: ${report.scores.cost}`,
|
|
237
|
+
`B. Turn fragmentation: ${report.scores.fragmentation}`,
|
|
238
|
+
`C. write_stdin churn: ${report.scores.writeStdin}`,
|
|
239
|
+
`D. Finish-path discipline: ${report.scores.finishPath}`,
|
|
240
|
+
`E. Post-proof drift: ${report.scores.postProof}`,
|
|
241
|
+
'',
|
|
242
|
+
`Total: ${report.scores.total}`,
|
|
243
|
+
`Label: ${report.label}`,
|
|
244
|
+
`Primary driver: ${report.primaryDriver}`,
|
|
245
|
+
`Secondary drivers: ${report.secondaries.length > 0 ? report.secondaries.join(', ') : 'none'}`,
|
|
246
|
+
].join('\n');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function renderSessionSeverityHelpDetails() {
|
|
250
|
+
const taskSizeDefaults = TASK_SIZE_VALUES
|
|
251
|
+
.map((taskSize) => `${taskSize}=${formatInteger(TASK_SIZE_UPPER_BOUNDS[taskSize])}`)
|
|
252
|
+
.join(', ');
|
|
253
|
+
const labelBands = LABEL_BANDS
|
|
254
|
+
.map((band, index) => {
|
|
255
|
+
const min = index === 0 ? 0 : LABEL_BANDS[index - 1].max + 1;
|
|
256
|
+
return `${band.label}=${min}-${band.max}`;
|
|
257
|
+
})
|
|
258
|
+
.join(', ');
|
|
259
|
+
return [`Task-size defaults: ${taskSizeDefaults}`, `Label bands: ${labelBands}`].join('\n');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function renderSessionSeverityCommand(toolName) {
|
|
263
|
+
return `${toolName} report ${SESSION_SEVERITY_COMMAND_TAIL}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderSessionSeverityExample(toolName) {
|
|
267
|
+
return `${toolName} report ${SESSION_SEVERITY_EXAMPLE_TAIL}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = {
|
|
271
|
+
LABEL_BANDS,
|
|
272
|
+
SESSION_SEVERITY_COMMAND_TAIL,
|
|
273
|
+
SESSION_SEVERITY_EXAMPLE_ARGS,
|
|
274
|
+
SESSION_SEVERITY_EXAMPLE_TAIL,
|
|
275
|
+
TASK_SIZE_UPPER_BOUNDS,
|
|
276
|
+
buildSessionSeverityReport,
|
|
277
|
+
renderSessionSeverityReport,
|
|
278
|
+
renderSessionSeverityHelpDetails,
|
|
279
|
+
renderSessionSeverityCommand,
|
|
280
|
+
renderSessionSeverityExample,
|
|
281
|
+
labelForTotal,
|
|
282
|
+
};
|
package/src/scaffold/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
2
|
fs,
|
|
3
3
|
path,
|
|
4
|
+
PACKAGE_ROOT,
|
|
4
5
|
TOOL_NAME,
|
|
5
6
|
SHORT_TOOL_NAME,
|
|
6
7
|
GUARDEX_HOME_DIR,
|
|
@@ -9,6 +10,7 @@ const {
|
|
|
9
10
|
HOOK_NAMES,
|
|
10
11
|
LOCK_FILE_RELATIVE,
|
|
11
12
|
LEGACY_MANAGED_PACKAGE_SCRIPTS,
|
|
13
|
+
PACKAGE_ROOT_SOURCE_OVERRIDES,
|
|
12
14
|
USER_LEVEL_SKILL_ASSETS,
|
|
13
15
|
AGENTS_MARKER_START,
|
|
14
16
|
AGENTS_MARKER_END,
|
|
@@ -24,6 +26,7 @@ const {
|
|
|
24
26
|
EXECUTABLE_RELATIVE_PATHS,
|
|
25
27
|
CRITICAL_GUARDRAIL_PATHS,
|
|
26
28
|
} = require('../context');
|
|
29
|
+
const { parse: parseJsonc, printParseErrorCode } = require('jsonc-parser');
|
|
27
30
|
const { run } = require('../core/runtime');
|
|
28
31
|
|
|
29
32
|
function ensureParentDir(repoRoot, filePath, dryRun) {
|
|
@@ -172,17 +175,13 @@ function ensureHookShim(repoRoot, hookName, options = {}) {
|
|
|
172
175
|
);
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
function
|
|
176
|
-
const
|
|
177
|
-
const destinationRelativePath = toDestinationPath(relativeTemplatePath);
|
|
178
|
-
const destinationPath = path.join(repoRoot, destinationRelativePath);
|
|
179
|
-
|
|
180
|
-
const sourceContent = fs.readFileSync(sourcePath, 'utf8');
|
|
178
|
+
function copyManagedSourceFile(repoRoot, sourcePath, destinationPath, destinationRelativePath, force, dryRun) {
|
|
179
|
+
const sourceContent = fs.readFileSync(sourcePath);
|
|
181
180
|
const destinationExists = fs.existsSync(destinationPath);
|
|
182
181
|
|
|
183
182
|
if (destinationExists) {
|
|
184
|
-
const existingContent = fs.readFileSync(destinationPath
|
|
185
|
-
if (existingContent
|
|
183
|
+
const existingContent = fs.readFileSync(destinationPath);
|
|
184
|
+
if (existingContent.equals(sourceContent)) {
|
|
186
185
|
ensureExecutable(destinationPath, destinationRelativePath, dryRun);
|
|
187
186
|
return { status: 'unchanged', file: destinationRelativePath };
|
|
188
187
|
}
|
|
@@ -193,7 +192,7 @@ function copyTemplateFile(repoRoot, relativeTemplatePath, force, dryRun) {
|
|
|
193
192
|
|
|
194
193
|
ensureParentDir(repoRoot, destinationPath, dryRun);
|
|
195
194
|
if (!dryRun) {
|
|
196
|
-
fs.writeFileSync(destinationPath, sourceContent
|
|
195
|
+
fs.writeFileSync(destinationPath, sourceContent);
|
|
197
196
|
ensureExecutable(destinationPath, destinationRelativePath, dryRun);
|
|
198
197
|
}
|
|
199
198
|
|
|
@@ -204,22 +203,54 @@ function copyTemplateFile(repoRoot, relativeTemplatePath, force, dryRun) {
|
|
|
204
203
|
return { status: destinationExists ? 'overwritten' : 'created', file: destinationRelativePath };
|
|
205
204
|
}
|
|
206
205
|
|
|
206
|
+
function normalizeTemplatePath(relativeTemplatePath) {
|
|
207
|
+
return String(relativeTemplatePath).replace(/\\/g, '/');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function usesPackageRootSource(repoRoot, relativeTemplatePath) {
|
|
211
|
+
return (
|
|
212
|
+
path.resolve(repoRoot) === PACKAGE_ROOT &&
|
|
213
|
+
PACKAGE_ROOT_SOURCE_OVERRIDES.has(normalizeTemplatePath(relativeTemplatePath))
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveTemplateSourcePath(repoRoot, relativeTemplatePath) {
|
|
218
|
+
if (usesPackageRootSource(repoRoot, relativeTemplatePath)) {
|
|
219
|
+
return path.join(PACKAGE_ROOT, relativeTemplatePath);
|
|
220
|
+
}
|
|
221
|
+
return path.join(TEMPLATE_ROOT, relativeTemplatePath);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function copyTemplateFile(repoRoot, relativeTemplatePath, force, dryRun) {
|
|
225
|
+
const sourcePath = resolveTemplateSourcePath(repoRoot, relativeTemplatePath);
|
|
226
|
+
const destinationRelativePath = toDestinationPath(relativeTemplatePath);
|
|
227
|
+
const destinationPath = path.join(repoRoot, destinationRelativePath);
|
|
228
|
+
return copyManagedSourceFile(
|
|
229
|
+
repoRoot,
|
|
230
|
+
sourcePath,
|
|
231
|
+
destinationPath,
|
|
232
|
+
destinationRelativePath,
|
|
233
|
+
force,
|
|
234
|
+
dryRun,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
207
238
|
function ensureTemplateFilePresent(repoRoot, relativeTemplatePath, dryRun) {
|
|
208
|
-
const sourcePath =
|
|
239
|
+
const sourcePath = resolveTemplateSourcePath(repoRoot, relativeTemplatePath);
|
|
209
240
|
const destinationRelativePath = toDestinationPath(relativeTemplatePath);
|
|
210
241
|
const destinationPath = path.join(repoRoot, destinationRelativePath);
|
|
211
|
-
const sourceContent = fs.readFileSync(sourcePath
|
|
242
|
+
const sourceContent = fs.readFileSync(sourcePath);
|
|
212
243
|
|
|
213
244
|
if (fs.existsSync(destinationPath)) {
|
|
214
|
-
const existingContent = fs.readFileSync(destinationPath
|
|
215
|
-
if (existingContent
|
|
245
|
+
const existingContent = fs.readFileSync(destinationPath);
|
|
246
|
+
if (existingContent.equals(sourceContent)) {
|
|
216
247
|
ensureExecutable(destinationPath, destinationRelativePath, dryRun);
|
|
217
248
|
return { status: 'unchanged', file: destinationRelativePath };
|
|
218
249
|
}
|
|
219
250
|
|
|
220
251
|
if (isCriticalGuardrailPath(destinationRelativePath)) {
|
|
221
252
|
if (!dryRun) {
|
|
222
|
-
fs.writeFileSync(destinationPath, sourceContent
|
|
253
|
+
fs.writeFileSync(destinationPath, sourceContent);
|
|
223
254
|
ensureExecutable(destinationPath, destinationRelativePath, dryRun);
|
|
224
255
|
}
|
|
225
256
|
return { status: dryRun ? 'would-repair-critical' : 'repaired-critical', file: destinationRelativePath };
|
|
@@ -230,13 +261,38 @@ function ensureTemplateFilePresent(repoRoot, relativeTemplatePath, dryRun) {
|
|
|
230
261
|
|
|
231
262
|
ensureParentDir(repoRoot, destinationPath, dryRun);
|
|
232
263
|
if (!dryRun) {
|
|
233
|
-
fs.writeFileSync(destinationPath, sourceContent
|
|
264
|
+
fs.writeFileSync(destinationPath, sourceContent);
|
|
234
265
|
ensureExecutable(destinationPath, destinationRelativePath, dryRun);
|
|
235
266
|
}
|
|
236
267
|
|
|
237
268
|
return { status: 'created', file: destinationRelativePath };
|
|
238
269
|
}
|
|
239
270
|
|
|
271
|
+
function materializePackageRepoTemplateFiles(repoRoot, relativeTemplatePaths, dryRun) {
|
|
272
|
+
if (path.resolve(repoRoot) !== PACKAGE_ROOT) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const operations = [];
|
|
277
|
+
for (const relativeTemplatePath of relativeTemplatePaths) {
|
|
278
|
+
if (!PACKAGE_ROOT_SOURCE_OVERRIDES.has(normalizeTemplatePath(relativeTemplatePath))) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const templateRelativePath = path.posix.join('templates', normalizeTemplatePath(relativeTemplatePath));
|
|
282
|
+
operations.push(
|
|
283
|
+
copyManagedSourceFile(
|
|
284
|
+
PACKAGE_ROOT,
|
|
285
|
+
path.join(PACKAGE_ROOT, relativeTemplatePath),
|
|
286
|
+
path.join(PACKAGE_ROOT, templateRelativePath),
|
|
287
|
+
templateRelativePath,
|
|
288
|
+
true,
|
|
289
|
+
dryRun,
|
|
290
|
+
),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return operations;
|
|
294
|
+
}
|
|
295
|
+
|
|
240
296
|
function lockFilePath(repoRoot) {
|
|
241
297
|
return path.join(repoRoot, LOCK_FILE_RELATIVE);
|
|
242
298
|
}
|
|
@@ -521,121 +577,13 @@ function ensureManagedGitignore(repoRoot, dryRun) {
|
|
|
521
577
|
return { status: 'updated', file: '.gitignore', note: 'appended gitguardex-managed entries' };
|
|
522
578
|
}
|
|
523
579
|
|
|
524
|
-
function stripJsonComments(source) {
|
|
525
|
-
let result = '';
|
|
526
|
-
let inString = false;
|
|
527
|
-
let escapeNext = false;
|
|
528
|
-
let inLineComment = false;
|
|
529
|
-
let inBlockComment = false;
|
|
530
|
-
|
|
531
|
-
for (let index = 0; index < source.length; index += 1) {
|
|
532
|
-
const current = source[index];
|
|
533
|
-
const next = source[index + 1];
|
|
534
|
-
|
|
535
|
-
if (inLineComment) {
|
|
536
|
-
if (current === '\n' || current === '\r') {
|
|
537
|
-
inLineComment = false;
|
|
538
|
-
result += current;
|
|
539
|
-
}
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (inBlockComment) {
|
|
544
|
-
if (current === '*' && next === '/') {
|
|
545
|
-
inBlockComment = false;
|
|
546
|
-
index += 1;
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
if (current === '\n' || current === '\r') {
|
|
550
|
-
result += current;
|
|
551
|
-
}
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (inString) {
|
|
556
|
-
result += current;
|
|
557
|
-
if (escapeNext) {
|
|
558
|
-
escapeNext = false;
|
|
559
|
-
} else if (current === '\\') {
|
|
560
|
-
escapeNext = true;
|
|
561
|
-
} else if (current === '"') {
|
|
562
|
-
inString = false;
|
|
563
|
-
}
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (current === '"') {
|
|
568
|
-
inString = true;
|
|
569
|
-
result += current;
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (current === '/' && next === '/') {
|
|
574
|
-
inLineComment = true;
|
|
575
|
-
index += 1;
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (current === '/' && next === '*') {
|
|
580
|
-
inBlockComment = true;
|
|
581
|
-
index += 1;
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
result += current;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
return result;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function stripJsonTrailingCommas(source) {
|
|
592
|
-
let result = '';
|
|
593
|
-
let inString = false;
|
|
594
|
-
let escapeNext = false;
|
|
595
|
-
|
|
596
|
-
for (let index = 0; index < source.length; index += 1) {
|
|
597
|
-
const current = source[index];
|
|
598
|
-
|
|
599
|
-
if (inString) {
|
|
600
|
-
result += current;
|
|
601
|
-
if (escapeNext) {
|
|
602
|
-
escapeNext = false;
|
|
603
|
-
} else if (current === '\\') {
|
|
604
|
-
escapeNext = true;
|
|
605
|
-
} else if (current === '"') {
|
|
606
|
-
inString = false;
|
|
607
|
-
}
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (current === '"') {
|
|
612
|
-
inString = true;
|
|
613
|
-
result += current;
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if (current === ',') {
|
|
618
|
-
let lookahead = index + 1;
|
|
619
|
-
while (lookahead < source.length && /\s/.test(source[lookahead])) {
|
|
620
|
-
lookahead += 1;
|
|
621
|
-
}
|
|
622
|
-
if (source[lookahead] === '}' || source[lookahead] === ']') {
|
|
623
|
-
continue;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
result += current;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
return result;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
580
|
function parseJsonObjectLikeFile(source, relativePath) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
581
|
+
const errors = [];
|
|
582
|
+
const parsed = parseJsonc(source, errors, { allowTrailingComma: true });
|
|
583
|
+
|
|
584
|
+
if (errors.length > 0) {
|
|
585
|
+
const formattedErrors = errors.map((entry) => printParseErrorCode(entry.error)).join(', ');
|
|
586
|
+
throw new Error(`Unable to parse ${relativePath} as JSON or JSONC: ${formattedErrors}`);
|
|
639
587
|
}
|
|
640
588
|
|
|
641
589
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
@@ -806,6 +754,7 @@ module.exports = {
|
|
|
806
754
|
ensureHookShim,
|
|
807
755
|
copyTemplateFile,
|
|
808
756
|
ensureTemplateFilePresent,
|
|
757
|
+
materializePackageRepoTemplateFiles,
|
|
809
758
|
ensureOmxScaffold,
|
|
810
759
|
ensureLockRegistry,
|
|
811
760
|
lockStateOrError,
|
|
@@ -815,8 +764,6 @@ module.exports = {
|
|
|
815
764
|
removeLegacyManagedRepoFile,
|
|
816
765
|
ensureAgentsSnippet,
|
|
817
766
|
ensureManagedGitignore,
|
|
818
|
-
stripJsonComments,
|
|
819
|
-
stripJsonTrailingCommas,
|
|
820
767
|
parseJsonObjectLikeFile,
|
|
821
768
|
buildRepoVscodeSettings,
|
|
822
769
|
ensureRepoVscodeSettings,
|
package/src/toolchain/index.js
CHANGED
|
@@ -17,50 +17,18 @@ const {
|
|
|
17
17
|
envFlagIsTruthy,
|
|
18
18
|
} = require('../context');
|
|
19
19
|
const { run } = require('../core/runtime');
|
|
20
|
+
const {
|
|
21
|
+
parseVersionString,
|
|
22
|
+
compareParsedVersions,
|
|
23
|
+
isNewerVersion,
|
|
24
|
+
} = require('../core/versions');
|
|
25
|
+
const { readSingleLineFromStdin } = require('../core/stdin');
|
|
20
26
|
const { colorize } = require('../output');
|
|
21
27
|
|
|
22
28
|
function isInteractiveTerminal() {
|
|
23
29
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
const stdinWaitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
27
|
-
|
|
28
|
-
function sleepSyncMs(milliseconds) {
|
|
29
|
-
Atomics.wait(stdinWaitArray, 0, 0, milliseconds);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readSingleLineFromStdin() {
|
|
33
|
-
let input = '';
|
|
34
|
-
const buffer = Buffer.alloc(1);
|
|
35
|
-
|
|
36
|
-
while (true) {
|
|
37
|
-
let bytesRead = 0;
|
|
38
|
-
try {
|
|
39
|
-
bytesRead = fs.readSync(process.stdin.fd, buffer, 0, 1);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
if (error && ['EAGAIN', 'EWOULDBLOCK', 'EINTR'].includes(error.code)) {
|
|
42
|
-
sleepSyncMs(15);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
return input;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (bytesRead === 0) {
|
|
49
|
-
if (process.stdin.isTTY) {
|
|
50
|
-
sleepSyncMs(15);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
return input;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const char = buffer.toString('utf8', 0, bytesRead);
|
|
57
|
-
if (char === '\n' || char === '\r') {
|
|
58
|
-
return input;
|
|
59
|
-
}
|
|
60
|
-
input += char;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
32
|
function parseAutoApproval(name) {
|
|
65
33
|
const raw = process.env[name];
|
|
66
34
|
if (raw == null) return null;
|
|
@@ -70,38 +38,6 @@ function parseAutoApproval(name) {
|
|
|
70
38
|
return null;
|
|
71
39
|
}
|
|
72
40
|
|
|
73
|
-
function parseVersionString(version) {
|
|
74
|
-
const match = String(version || '').trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
75
|
-
if (!match) return null;
|
|
76
|
-
return [
|
|
77
|
-
Number.parseInt(match[1], 10),
|
|
78
|
-
Number.parseInt(match[2], 10),
|
|
79
|
-
Number.parseInt(match[3], 10),
|
|
80
|
-
];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function compareParsedVersions(left, right) {
|
|
84
|
-
if (!left || !right) return 0;
|
|
85
|
-
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
86
|
-
const leftValue = left[index] || 0;
|
|
87
|
-
const rightValue = right[index] || 0;
|
|
88
|
-
if (leftValue > rightValue) return 1;
|
|
89
|
-
if (leftValue < rightValue) return -1;
|
|
90
|
-
}
|
|
91
|
-
return 0;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isNewerVersion(latest, current) {
|
|
95
|
-
const latestParts = parseVersionString(latest);
|
|
96
|
-
const currentParts = parseVersionString(current);
|
|
97
|
-
|
|
98
|
-
if (!latestParts || !currentParts) {
|
|
99
|
-
return String(latest || '').trim() !== String(current || '').trim();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return compareParsedVersions(latestParts, currentParts) > 0;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
41
|
function parseNpmVersionOutput(stdout) {
|
|
106
42
|
const trimmed = String(stdout || '').trim();
|
|
107
43
|
if (!trimmed) return '';
|