agentic-workflow-manager 9.7.1 → 9.8.0
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 +5 -1
- package/dist/src/commands/job/exec-wrapper.js +53 -3
- package/dist/src/commands/plan/index.js +124 -6
- package/dist/src/commands/sensors/bootstrap.js +36 -5
- package/dist/src/commands/sensors/compatibility/contract.js +19 -4
- package/dist/src/commands/sensors/compatibility/discovery.js +23 -3
- package/dist/src/commands/sensors/compatibility/live.js +18 -1
- package/dist/src/commands/sensors/compatibility/probe.js +8 -2
- package/dist/src/commands/sensors/compatibility/resolve.js +5 -2
- package/dist/src/commands/sensors/coverage/evidence.js +19 -10
- package/dist/src/commands/sensors/coverage/index.js +11 -2
- package/dist/src/commands/sensors/coverage/resolve.js +10 -0
- package/dist/src/commands/sensors/exec.js +16 -1
- package/dist/src/commands/sensors/migrate.js +62 -2
- package/dist/src/commands/sensors/prepare.js +2 -0
- package/dist/src/commands/sensors/result.js +2 -0
- package/dist/src/commands/sensors/run.js +64 -1
- package/dist/src/commands/sensors/status.js +6 -4
- package/dist/src/commands/watch/archive-unused.js +138 -0
- package/dist/src/commands/watch/generations.js +32 -9
- package/dist/src/commands/watch/index.js +108 -2
- package/dist/src/commands/watch/init.js +149 -18
- package/dist/src/commands/watch/supervisor.js +167 -13
- package/dist/src/commands/watch/teardown-driver.js +13 -13
- package/dist/src/commands/watch/track-state.js +59 -0
- package/dist/src/commands/watch/tracks.js +38 -102
- package/dist/src/core/admission/index.js +181 -0
- package/dist/src/core/admission/registry-contracts.js +290 -0
- package/dist/src/core/journal/fingerprint.js +35 -1
- package/dist/src/core/journal/paths.js +11 -0
- package/dist/src/core/journal/store.js +146 -4
- package/dist/src/core/journal/types.js +35 -1
- package/dist/src/core/migration/historical-provenance.js +18 -0
- package/dist/src/core/migration/index.js +322 -0
- package/dist/src/core/plan/identity.js +75 -0
- package/dist/src/core/plan/validate.js +178 -64
- package/dist/tests/commands/job/exec-wrapper.test.js +16 -0
- package/dist/tests/commands/job/gate-reconcile.test.js +24 -10
- package/dist/tests/commands/pin.test.js +23 -23
- package/dist/tests/commands/plan/index.test.js +630 -14
- package/dist/tests/commands/sensors/bootstrap.test.js +57 -4
- package/dist/tests/commands/sensors/compatibility/contract.test.js +21 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +19 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +14 -1
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +17 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +51 -27
- package/dist/tests/commands/sensors/coverage/resolve.test.js +51 -0
- package/dist/tests/commands/sensors/exec.test.js +112 -0
- package/dist/tests/commands/sensors/run-changed.test.js +11 -0
- package/dist/tests/commands/sensors/run.test.js +15 -2
- package/dist/tests/commands/sensors/status.test.js +3 -3
- package/dist/tests/commands/watch/e2e-crash.test.js +126 -11
- package/dist/tests/commands/watch/generations.test.js +113 -4
- package/dist/tests/commands/watch/plan-lifecycle.test.js +263 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +247 -49
- package/dist/tests/commands/watch/track-finalize.test.js +20 -3
- package/dist/tests/commands/watch/track-freeze.test.js +43 -30
- package/dist/tests/commands/watch/track-state-dependency.test.js +15 -0
- package/dist/tests/commands/watch/watch-init.test.js +244 -0
- package/dist/tests/core/admission/index.test.js +87 -0
- package/dist/tests/core/journal/fingerprint.test.js +22 -0
- package/dist/tests/core/journal/store.test.js +53 -0
- package/dist/tests/core/journal/types.test.js +7 -0
- package/dist/tests/core/migration/facts.test.js +289 -0
- package/dist/tests/core/migration/historical-provenance.test.js +25 -0
- package/dist/tests/core/plan/validate.test.js +320 -6
- package/dist/tests/integration/pack-e2e.test.js +4 -1
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +7 -3
- package/dist/tests/integration/sensor-bootstrap.e2e.test.js +37 -0
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +12 -10
- package/dist/tests/providers/injection-config.test.js +14 -6
- package/dist/tests/structural/jest-environment-is-isolated.test.js +29 -24
- package/dist/tests/structural/native-release-artifacts.test.js +16 -0
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.EXECUTION_IDENTITY_SCHEMA = exports.MAX_PLAN_SNAPSHOT_BYTES = void 0;
|
|
7
|
+
exports.normalizedPlanSnapshot = normalizedPlanSnapshot;
|
|
8
|
+
exports.fullPlanDigest = fullPlanDigest;
|
|
9
|
+
exports.executionPlanDigest = executionPlanDigest;
|
|
10
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
11
|
+
exports.MAX_PLAN_SNAPSHOT_BYTES = 1024 * 1024;
|
|
12
|
+
exports.EXECUTION_IDENTITY_SCHEMA = 'awm-plan-execution/v1';
|
|
13
|
+
const START = '<!-- AWM:COMPACT-SLICES:START v1 -->';
|
|
14
|
+
const END = '<!-- AWM:COMPACT-SLICES:END v1 -->';
|
|
15
|
+
/** Only lifecycle syntax actually emitted by the skills is non-executable.
|
|
16
|
+
* Unknown metadata, prose, inline comments and code remain identity-bearing. */
|
|
17
|
+
function lifecycleMarker(line) {
|
|
18
|
+
const match = /^ {0,3}<!-- (awm-(?:qa|docs|retro)-complete)(?:: (\d{4}-\d{2}-\d{2}|Release [A-Za-z0-9][A-Za-z0-9_-]*))? -->$/.exec(line);
|
|
19
|
+
if (!match)
|
|
20
|
+
return undefined;
|
|
21
|
+
const metadata = match[2];
|
|
22
|
+
if (metadata && !metadata.startsWith('Release ')) {
|
|
23
|
+
const parsed = new Date(`${metadata}T00:00:00.000Z`);
|
|
24
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== metadata)
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return `${match[1]}:${metadata?.startsWith('Release ') ? metadata : ''}`;
|
|
28
|
+
}
|
|
29
|
+
function normalizedPlanSnapshot(text) {
|
|
30
|
+
if (typeof text !== 'string' || text.length === 0 || Buffer.byteLength(text, 'utf8') > exports.MAX_PLAN_SNAPSHOT_BYTES || text.includes('\0')) {
|
|
31
|
+
throw new Error('plan snapshot must be nonempty bounded text without NUL');
|
|
32
|
+
}
|
|
33
|
+
return text.replace(/\r\n?/g, '\n');
|
|
34
|
+
}
|
|
35
|
+
function fullPlanDigest(text) {
|
|
36
|
+
return crypto_1.default.createHash('sha256').update(normalizedPlanSnapshot(text), 'utf8').digest('hex');
|
|
37
|
+
}
|
|
38
|
+
/** This versioned identity preserves ALL plan bytes except recognized progress
|
|
39
|
+
* status bytes and unique standalone lifecycle marker lines outside code/JSON.
|
|
40
|
+
* It is never a replacement for the full validated-plan digest. */
|
|
41
|
+
function executionPlanDigest(text) {
|
|
42
|
+
const lines = normalizedPlanSnapshot(text).split('\n');
|
|
43
|
+
let fence;
|
|
44
|
+
let manifest = false;
|
|
45
|
+
const candidates = lines.map(line => {
|
|
46
|
+
const matched = /^(?: {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
|
|
47
|
+
const marker = matched?.[1];
|
|
48
|
+
if (marker && (fence
|
|
49
|
+
? marker[0] === fence.character && marker.length >= fence.length && /^[ \t]*$/.test(matched[2])
|
|
50
|
+
: marker[0] !== '`' || !matched[2].includes('`'))) {
|
|
51
|
+
fence = fence ? undefined : { character: marker[0], length: marker.length };
|
|
52
|
+
return { line };
|
|
53
|
+
}
|
|
54
|
+
if (fence)
|
|
55
|
+
return { line };
|
|
56
|
+
if (line === START) {
|
|
57
|
+
manifest = true;
|
|
58
|
+
return { line };
|
|
59
|
+
}
|
|
60
|
+
if (line === END) {
|
|
61
|
+
manifest = false;
|
|
62
|
+
return { line };
|
|
63
|
+
}
|
|
64
|
+
if (manifest)
|
|
65
|
+
return { line };
|
|
66
|
+
const lifecycle = lifecycleMarker(line);
|
|
67
|
+
return { line: line.replace(/^( {0,3}[-*+] +)\[[ xX]\]( +\S.*)$/, '$1[ ]$2'), lifecycle };
|
|
68
|
+
});
|
|
69
|
+
const counts = new Map();
|
|
70
|
+
for (const { lifecycle } of candidates)
|
|
71
|
+
if (lifecycle)
|
|
72
|
+
counts.set(lifecycle, (counts.get(lifecycle) ?? 0) + 1);
|
|
73
|
+
const canonical = candidates.filter(({ lifecycle }) => !lifecycle || counts.get(lifecycle) !== 1).map(({ line }) => line).join('\n');
|
|
74
|
+
return crypto_1.default.createHash('sha256').update(`${exports.EXECUTION_IDENTITY_SCHEMA}\0`).update(canonical, 'utf8').digest('hex');
|
|
75
|
+
}
|
|
@@ -3,22 +3,51 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.assertVerifiedValidPlanReport = assertVerifiedValidPlanReport;
|
|
7
|
+
exports.verifiedPlanSnapshot = verifiedPlanSnapshot;
|
|
6
8
|
exports.validatePlanFile = validatePlanFile;
|
|
9
|
+
exports.validatePlanSnapshot = validatePlanSnapshot;
|
|
7
10
|
const fs_1 = __importDefault(require("fs"));
|
|
8
11
|
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
13
|
const json_1 = require("./json");
|
|
14
|
+
const identity_1 = require("./identity");
|
|
10
15
|
const START = '<!-- AWM:COMPACT-SLICES:START v1 -->';
|
|
11
16
|
const END = '<!-- AWM:COMPACT-SLICES:END v1 -->';
|
|
12
17
|
const MAX_PLAN = 1024 * 1024;
|
|
13
18
|
const MAX_MANIFEST = 256 * 1024;
|
|
14
19
|
const MAX_SOURCE = 1024 * 1024;
|
|
15
20
|
const MAX_STRING = 4096;
|
|
16
|
-
const
|
|
21
|
+
const ENTITY_ID = /^[A-Z][A-Z0-9-]{0,63}$/;
|
|
22
|
+
const REQUIREMENT_ID = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*(?:\.[A-Z0-9]+(?:-[A-Z0-9]+)*)*$/;
|
|
17
23
|
const PLAN_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
18
24
|
const SHELL = new Set([
|
|
19
25
|
'sh', 'bash', 'zsh', 'dash', 'ksh', 'fish', 'cmd', 'powershell', 'pwsh',
|
|
20
26
|
'env', 'busybox', 'node', 'nodejs', 'deno', 'bun', 'python', 'python3', 'ruby', 'perl', 'php', 'lua',
|
|
21
27
|
]);
|
|
28
|
+
const verifiedValidReports = new WeakSet();
|
|
29
|
+
const verifiedSnapshots = new WeakMap();
|
|
30
|
+
function freezeJson(value) {
|
|
31
|
+
if (value === null || typeof value !== 'object')
|
|
32
|
+
return;
|
|
33
|
+
for (const child of Object.values(value))
|
|
34
|
+
freezeJson(child);
|
|
35
|
+
Object.freeze(value);
|
|
36
|
+
}
|
|
37
|
+
/** Require a process-local, immutable report issued by successful core validation. */
|
|
38
|
+
function assertVerifiedValidPlanReport(report) {
|
|
39
|
+
if (!report || typeof report !== 'object' || report.state !== 'valid' || !verifiedValidReports.has(report)) {
|
|
40
|
+
throw new Error('plan validator returned an invalid valid report');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** The exact immutable validator snapshot, never a later reopen of the path. */
|
|
44
|
+
function verifiedPlanSnapshot(report) {
|
|
45
|
+
assertVerifiedValidPlanReport(report);
|
|
46
|
+
const snapshot = verifiedSnapshots.get(report);
|
|
47
|
+
if (snapshot === undefined)
|
|
48
|
+
throw new Error('verified plan snapshot unavailable');
|
|
49
|
+
return snapshot;
|
|
50
|
+
}
|
|
22
51
|
function isLauncher(program) {
|
|
23
52
|
const base = path_1.default.posix.basename(program).toLowerCase();
|
|
24
53
|
return SHELL.has(base.replace(/\.exe$/, '')) || /^(?:node(?:js)?|py(?:thon(?:w)?\d*(?:\.\d+)?)?|deno|bun|ruby(?:\d+(?:\.\d+)*)?|perl(?:\d+(?:\.\d+)*)?|(?:a|ba|c|tc|z|da|k)?sh|busybox|cmd|powershell|pwsh|wscript|cscript)(?:\.exe)?(?:[-.][a-z0-9][a-z0-9.-]*)?$/i.test(base);
|
|
@@ -37,39 +66,49 @@ function schemaClassification(candidate) {
|
|
|
37
66
|
return diagnostic('PLAN_MARKERS', 'compact markers must occur once in order');
|
|
38
67
|
}
|
|
39
68
|
function embeddedJsonObjects(text) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (; end < text.length && end - start <= MAX_MANIFEST; end += 1) {
|
|
49
|
-
const character = text[end];
|
|
50
|
-
if (quoted) {
|
|
51
|
-
if (escaped)
|
|
52
|
-
escaped = false;
|
|
53
|
-
else if (character === '\\')
|
|
54
|
-
escaped = true;
|
|
55
|
-
else if (character === '"')
|
|
56
|
-
quoted = false;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (character === '"') {
|
|
60
|
-
quoted = true;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
69
|
+
const outer = [];
|
|
70
|
+
const nested = [];
|
|
71
|
+
const starts = [];
|
|
72
|
+
let quoted = false;
|
|
73
|
+
let escaped = false;
|
|
74
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
75
|
+
const character = text[index];
|
|
76
|
+
if (starts.length === 0) {
|
|
63
77
|
if (character === '{')
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
starts.push(index);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (quoted) {
|
|
82
|
+
if (character === '\n') {
|
|
83
|
+
quoted = false;
|
|
84
|
+
escaped = false;
|
|
69
85
|
}
|
|
86
|
+
else if (escaped)
|
|
87
|
+
escaped = false;
|
|
88
|
+
else if (character === '\\')
|
|
89
|
+
escaped = true;
|
|
90
|
+
else if (character === '"')
|
|
91
|
+
quoted = false;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (character === '"') {
|
|
95
|
+
quoted = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (character === '{') {
|
|
99
|
+
starts.push(index);
|
|
100
|
+
continue;
|
|
70
101
|
}
|
|
102
|
+
if (character !== '}')
|
|
103
|
+
continue;
|
|
104
|
+
const start = starts.pop();
|
|
105
|
+
if (start === undefined || index - start > MAX_MANIFEST)
|
|
106
|
+
continue;
|
|
107
|
+
const found = starts.length === 0 ? outer : nested;
|
|
108
|
+
if (found.length < 64)
|
|
109
|
+
found.push(text.slice(start, index + 1));
|
|
71
110
|
}
|
|
72
|
-
return
|
|
111
|
+
return [...outer, ...nested].slice(0, 64);
|
|
73
112
|
}
|
|
74
113
|
function markerlessSchemaClassification(text) {
|
|
75
114
|
const candidates = [text.trim(), ...embeddedJsonObjects(text)];
|
|
@@ -123,11 +162,18 @@ function compactSchemaSignal(text) {
|
|
|
123
162
|
}
|
|
124
163
|
return false;
|
|
125
164
|
}
|
|
165
|
+
function executionModeFromValidatedText(text) {
|
|
166
|
+
const raw = /^\s*\*\*Modo de ejecución:\*\*\s*([^\r\n]+)\s*$/mi.exec(text)?.[1]?.trim().replace(/^`|`$/g, '');
|
|
167
|
+
return raw === 'desatendido' ? 'desatendido' : 'interactivo';
|
|
168
|
+
}
|
|
126
169
|
function exact(value, fields) {
|
|
127
170
|
return object(value) && Object.keys(value).length === fields.length && fields.every((field) => Object.prototype.hasOwnProperty.call(value, field));
|
|
128
171
|
}
|
|
129
172
|
function allStrings(value, max = MAX_STRING) { return Array.isArray(value) && value.every((item) => typeof item === 'string' && Buffer.byteLength(item, 'utf8') <= max); }
|
|
130
|
-
function validId(value) { return typeof value === 'string' &&
|
|
173
|
+
function validId(value) { return typeof value === 'string' && ENTITY_ID.test(value); }
|
|
174
|
+
function validRequirementId(value) {
|
|
175
|
+
return typeof value === 'string' && Buffer.byteLength(value, 'utf8') <= 64 && REQUIREMENT_ID.test(value);
|
|
176
|
+
}
|
|
131
177
|
function inside(root, candidate) { const relative = path_1.default.relative(root, candidate); return relative === '' || (!relative.startsWith(`..${path_1.default.sep}`) && relative !== '..' && !path_1.default.isAbsolute(relative)); }
|
|
132
178
|
function validRelative(value) {
|
|
133
179
|
return typeof value === 'string' && value.length > 0 && Buffer.byteLength(value, 'utf8') <= MAX_STRING && !path_1.default.isAbsolute(value) && !path_1.default.win32.isAbsolute(value) && !value.includes('\\') && value.split('/').every((part) => part !== '' && part !== '.' && part !== '..') && path_1.default.posix.normalize(value) === value;
|
|
@@ -140,30 +186,74 @@ function regularInside(root, relative) {
|
|
|
140
186
|
return undefined;
|
|
141
187
|
const parts = relative.split('/');
|
|
142
188
|
let current = root;
|
|
189
|
+
let stat;
|
|
143
190
|
try {
|
|
144
191
|
for (const part of parts) {
|
|
145
192
|
current = path_1.default.join(current, part);
|
|
146
|
-
|
|
193
|
+
stat = fs_1.default.lstatSync(current, { bigint: true });
|
|
194
|
+
if (stat.isSymbolicLink())
|
|
147
195
|
return undefined;
|
|
148
196
|
}
|
|
149
|
-
if (!
|
|
197
|
+
if (!stat?.isFile())
|
|
150
198
|
return undefined;
|
|
151
199
|
const real = fs_1.default.realpathSync(candidate);
|
|
152
|
-
return inside(root, real) ? real : undefined;
|
|
200
|
+
return inside(root, real) ? { path: real, stat } : undefined;
|
|
153
201
|
}
|
|
154
202
|
catch {
|
|
155
203
|
return undefined;
|
|
156
204
|
}
|
|
157
205
|
}
|
|
158
|
-
function
|
|
159
|
-
|
|
206
|
+
function sameIdentity(inspected, opened) {
|
|
207
|
+
return typeof inspected.dev === 'bigint' && typeof inspected.ino === 'bigint' && inspected.ino !== 0n
|
|
208
|
+
&& opened.isFile() && inspected.dev === opened.dev && inspected.ino === opened.ino;
|
|
209
|
+
}
|
|
210
|
+
function readInspected(file, max) {
|
|
211
|
+
if (file.stat.size > BigInt(max))
|
|
212
|
+
return { state: 'limit' };
|
|
213
|
+
let descriptor;
|
|
160
214
|
try {
|
|
161
|
-
|
|
215
|
+
descriptor = fs_1.default.openSync(file.path, fs_1.default.constants.O_RDONLY | (fs_1.default.constants.O_NOFOLLOW ?? 0));
|
|
216
|
+
const opened = fs_1.default.fstatSync(descriptor, { bigint: true });
|
|
217
|
+
if (!sameIdentity(file.stat, opened))
|
|
218
|
+
return { state: 'unsafe' };
|
|
219
|
+
if (opened.size > BigInt(max))
|
|
220
|
+
return { state: 'limit' };
|
|
221
|
+
if (opened.size !== file.stat.size)
|
|
222
|
+
return { state: 'unsafe' };
|
|
223
|
+
const bytes = Buffer.alloc(Number(opened.size));
|
|
224
|
+
let offset = 0;
|
|
225
|
+
while (offset < bytes.length) {
|
|
226
|
+
const read = fs_1.default.readSync(descriptor, bytes, offset, bytes.length - offset, offset);
|
|
227
|
+
if (read <= 0)
|
|
228
|
+
return { state: 'read' };
|
|
229
|
+
offset += read;
|
|
230
|
+
}
|
|
231
|
+
const after = fs_1.default.fstatSync(descriptor, { bigint: true });
|
|
232
|
+
if (!sameIdentity(file.stat, after))
|
|
233
|
+
return { state: 'unsafe' };
|
|
234
|
+
if (after.size > BigInt(max) || bytes.length > max)
|
|
235
|
+
return { state: 'limit' };
|
|
236
|
+
if (after.size !== opened.size)
|
|
237
|
+
return { state: 'unsafe' };
|
|
238
|
+
return { state: 'ok', bytes };
|
|
162
239
|
}
|
|
163
|
-
catch {
|
|
164
|
-
|
|
240
|
+
catch (error) {
|
|
241
|
+
const code = error.code;
|
|
242
|
+
return { state: code === 'ELOOP' || code === 'ENOENT' || code === 'ENOTDIR' ? 'unsafe' : 'read' };
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
if (descriptor !== undefined) {
|
|
246
|
+
try {
|
|
247
|
+
fs_1.default.closeSync(descriptor);
|
|
248
|
+
}
|
|
249
|
+
catch { /* best-effort close after a failed inspected read */ }
|
|
250
|
+
}
|
|
165
251
|
}
|
|
166
252
|
}
|
|
253
|
+
function executableInside(root, relative) {
|
|
254
|
+
const file = regularInside(root, relative);
|
|
255
|
+
return file && (process.platform === 'win32' ? /\.(?:exe|cmd|bat)$/i.test(file.path) : (file.stat.mode & 73n) !== 0n) ? file.path : undefined;
|
|
256
|
+
}
|
|
167
257
|
function refs(value, known) { return allStrings(value) && value.every((id) => known.has(id)); }
|
|
168
258
|
function uniqueRefs(value, known) { return refs(value, known) && new Set(value).size === value.length; }
|
|
169
259
|
function unsafeCommand(value) { return /[\0\r\n]|[`$]|\$\(|\$\{|[;&|<>*?\[\]{}()]/.test(value); }
|
|
@@ -219,8 +309,8 @@ function checkMarkdown(text, slices) {
|
|
|
219
309
|
}
|
|
220
310
|
return undefined;
|
|
221
311
|
}
|
|
222
|
-
/**
|
|
223
|
-
function
|
|
312
|
+
/** Shared parser. A supplied snapshot is already descriptor-anchored by its caller. */
|
|
313
|
+
function validatePlan(planPath, cwd, snapshot) {
|
|
224
314
|
if (typeof planPath !== 'string' || planPath.length === 0)
|
|
225
315
|
throw new Error('planPath must be a non-empty path');
|
|
226
316
|
if (typeof cwd !== 'string' || cwd.length === 0)
|
|
@@ -238,21 +328,28 @@ function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
|
238
328
|
if (!inside(root, candidate))
|
|
239
329
|
return diagnostic('PLAN_PATH_UNSAFE', 'plan path must be inside cwd');
|
|
240
330
|
const relativePlan = path_1.default.relative(root, candidate).replace(/\\/g, '/');
|
|
241
|
-
const planFile = regularInside(root, relativePlan);
|
|
242
|
-
if (!planFile)
|
|
243
|
-
return diagnostic('PLAN_PATH_UNSAFE', 'plan path must be a contained regular non-symlink file');
|
|
244
331
|
let bytes;
|
|
245
|
-
|
|
246
|
-
if (
|
|
332
|
+
if (snapshot) {
|
|
333
|
+
if (snapshot.length > MAX_PLAN)
|
|
247
334
|
return diagnostic('PLAN_LIMIT', 'plan exceeds maximum size');
|
|
248
|
-
bytes =
|
|
335
|
+
bytes = Buffer.from(snapshot);
|
|
249
336
|
}
|
|
250
|
-
|
|
251
|
-
|
|
337
|
+
else {
|
|
338
|
+
const planFile = regularInside(root, relativePlan);
|
|
339
|
+
if (!planFile)
|
|
340
|
+
return diagnostic('PLAN_PATH_UNSAFE', 'plan path must be a contained regular non-symlink file');
|
|
341
|
+
const planRead = readInspected(planFile, MAX_PLAN);
|
|
342
|
+
if (planRead.state === 'unsafe')
|
|
343
|
+
return diagnostic('PLAN_PATH_UNSAFE', 'plan path changed after inspection');
|
|
344
|
+
if (planRead.state === 'limit')
|
|
345
|
+
return diagnostic('PLAN_LIMIT', 'plan exceeds maximum size');
|
|
346
|
+
if (planRead.state === 'read')
|
|
347
|
+
return diagnostic('PLAN_READ', 'plan cannot be read');
|
|
348
|
+
bytes = planRead.bytes;
|
|
252
349
|
}
|
|
253
350
|
let text;
|
|
254
351
|
try {
|
|
255
|
-
text = normalizeLineEndings(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
352
|
+
text = normalizeLineEndings(new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes));
|
|
256
353
|
}
|
|
257
354
|
catch {
|
|
258
355
|
return diagnostic('PLAN_ENCODING', 'plan must be valid UTF-8');
|
|
@@ -261,7 +358,7 @@ function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
|
261
358
|
const ends = count(text, END);
|
|
262
359
|
const signaled = starts > 0 || ends > 0 || compactSchemaSignal(text);
|
|
263
360
|
if (!signaled)
|
|
264
|
-
return markerlessSchemaClassification(text) ?? { state: '
|
|
361
|
+
return markerlessSchemaClassification(text) ?? { state: 'migration-required', reason: 'unmarked-plan' };
|
|
265
362
|
if (starts === 0 && ends === 0)
|
|
266
363
|
return markerlessSchemaClassification(text) ?? diagnostic('PLAN_MARKERS', 'compact markers must occur once in order');
|
|
267
364
|
if (starts !== 1 || ends !== 1 || text.indexOf(START) > text.indexOf(END)) {
|
|
@@ -300,7 +397,7 @@ function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
|
300
397
|
return diagnostic('PLAN_LIMIT', 'plan id exceeds maximum string size');
|
|
301
398
|
if (raw.schema !== 'compact-slices/v1' || typeof raw.planId !== 'string' || !PLAN_ID.test(raw.planId) || !allStrings(raw.requirements) || new Set(raw.requirements).size !== raw.requirements.length)
|
|
302
399
|
return diagnostic('PLAN_SHAPE', 'manifest scalar fields are invalid');
|
|
303
|
-
if (raw.requirements.some((id) => !
|
|
400
|
+
if (raw.requirements.some((id) => !validRequirementId(id)) || !allStrings(raw.closureCommands) || new Set(raw.closureCommands).size !== raw.closureCommands.length)
|
|
304
401
|
return diagnostic('PLAN_SHAPE', 'manifest arrays or identifiers are invalid');
|
|
305
402
|
const sourceIds = new Set();
|
|
306
403
|
for (const source of raw.sources) {
|
|
@@ -310,18 +407,16 @@ function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
|
310
407
|
const file = regularInside(root, source.path);
|
|
311
408
|
if (!file)
|
|
312
409
|
return diagnostic('PLAN_SOURCE_UNSAFE', 'source path must be a contained regular non-symlink file');
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
catch {
|
|
410
|
+
const sourceRead = readInspected(file, MAX_SOURCE);
|
|
411
|
+
if (sourceRead.state === 'unsafe')
|
|
412
|
+
return diagnostic('PLAN_SOURCE_UNSAFE', 'source path changed after inspection');
|
|
413
|
+
if (sourceRead.state === 'limit')
|
|
414
|
+
return diagnostic('PLAN_SOURCE_LIMIT', 'source exceeds maximum size');
|
|
415
|
+
if (sourceRead.state === 'read')
|
|
320
416
|
return diagnostic('PLAN_SOURCE_UNSAFE', 'source cannot be read');
|
|
321
|
-
}
|
|
322
417
|
let contents;
|
|
323
418
|
try {
|
|
324
|
-
contents = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
419
|
+
contents = new TextDecoder('utf-8', { fatal: true }).decode(sourceRead.bytes);
|
|
325
420
|
}
|
|
326
421
|
catch {
|
|
327
422
|
return diagnostic('PLAN_SOURCE_ENCODING', 'source must be valid UTF-8');
|
|
@@ -332,7 +427,7 @@ function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
|
332
427
|
const commandIds = new Set();
|
|
333
428
|
const closureIds = new Set(raw.closureCommands);
|
|
334
429
|
for (const command of raw.commands) {
|
|
335
|
-
if (!exact(command, ['id', 'program', 'args', 'covers']) || !validId(command.id) || commandIds.has(command.id) || typeof command.program !== 'string' || command.program.length === 0 || Buffer.byteLength(command.program, 'utf8') > MAX_STRING || !allStrings(command.args) || command.args.length > 128 || !allStrings(command.covers) || new Set(command.covers).size !== command.covers.length || (command.covers.length === 0 && !closureIds.has(command.id)) || command.covers.some((id) => !raw.requirements.includes(id)))
|
|
430
|
+
if (!exact(command, ['id', 'program', 'args', 'covers']) || !validId(command.id) || commandIds.has(command.id) || typeof command.program !== 'string' || command.program.length === 0 || Buffer.byteLength(command.program, 'utf8') > MAX_STRING || !allStrings(command.args) || command.args.length > 128 || !allStrings(command.covers) || new Set(command.covers).size !== command.covers.length || (command.covers.length === 0 && !closureIds.has(command.id)) || command.covers.some((id) => !validRequirementId(id) || !raw.requirements.includes(id)))
|
|
336
431
|
return diagnostic('PLAN_COMMAND_SHAPE', 'command fields are invalid');
|
|
337
432
|
commandIds.add(command.id);
|
|
338
433
|
const program = command.program;
|
|
@@ -344,7 +439,7 @@ function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
|
344
439
|
const sliceIds = new Set();
|
|
345
440
|
const owners = new Map();
|
|
346
441
|
for (const slice of raw.slices) {
|
|
347
|
-
if (!exact(slice, ['id', 'title', 'requirements', 'dependsOn', 'sectionAnchor', 'sources', 'redCommands', 'greenCommands', 'reviewEvidence', 'risk', 'fallback']) || !validId(slice.id) || sliceIds.has(slice.id) || typeof slice.title !== 'string' || slice.title.length === 0 || Buffer.byteLength(slice.title, 'utf8') > MAX_STRING || !allStrings(slice.requirements) || slice.requirements.length === 0 || slice.requirements.some((id) => !raw.requirements.includes(id)) || !allStrings(slice.dependsOn) || typeof slice.sectionAnchor !== 'string' || slice.sectionAnchor.length === 0 || Buffer.byteLength(slice.sectionAnchor, 'utf8') > MAX_STRING || !uniqueRefs(slice.sources, sourceIds) || !uniqueRefs(slice.redCommands, commandIds) || slice.redCommands.length === 0 || !uniqueRefs(slice.greenCommands, commandIds) || slice.greenCommands.length === 0 || !allStrings(slice.fallback) || slice.fallback.length === 0 || slice.fallback.some((item) => item.trim().length === 0) || slice.risk !== 'bounded' && slice.risk !== 'full-context')
|
|
442
|
+
if (!exact(slice, ['id', 'title', 'requirements', 'dependsOn', 'sectionAnchor', 'sources', 'redCommands', 'greenCommands', 'reviewEvidence', 'risk', 'fallback']) || !validId(slice.id) || sliceIds.has(slice.id) || typeof slice.title !== 'string' || slice.title.length === 0 || Buffer.byteLength(slice.title, 'utf8') > MAX_STRING || !allStrings(slice.requirements) || slice.requirements.length === 0 || slice.requirements.some((id) => !validRequirementId(id) || !raw.requirements.includes(id)) || !allStrings(slice.dependsOn) || typeof slice.sectionAnchor !== 'string' || slice.sectionAnchor.length === 0 || Buffer.byteLength(slice.sectionAnchor, 'utf8') > MAX_STRING || !uniqueRefs(slice.sources, sourceIds) || !uniqueRefs(slice.redCommands, commandIds) || slice.redCommands.length === 0 || !uniqueRefs(slice.greenCommands, commandIds) || slice.greenCommands.length === 0 || !allStrings(slice.fallback) || slice.fallback.length === 0 || slice.fallback.some((item) => item.trim().length === 0) || slice.risk !== 'bounded' && slice.risk !== 'full-context')
|
|
348
443
|
return diagnostic('PLAN_SLICE_SHAPE', 'slice fields are invalid');
|
|
349
444
|
sliceIds.add(slice.id);
|
|
350
445
|
if (!Array.isArray(slice.reviewEvidence) || slice.reviewEvidence.length !== 2 || new Set(slice.reviewEvidence).size !== 2 || !slice.reviewEvidence.includes('specification') || !slice.reviewEvidence.includes('code-quality'))
|
|
@@ -389,5 +484,24 @@ function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
|
389
484
|
const markdown = checkMarkdown(text, slices);
|
|
390
485
|
if (markdown)
|
|
391
486
|
return markdown;
|
|
392
|
-
|
|
487
|
+
const report = {
|
|
488
|
+
state: 'valid', schema: 'compact-slices/v1',
|
|
489
|
+
planDigest: crypto_1.default.createHash('sha256').update(text, 'utf8').digest('hex'),
|
|
490
|
+
executionDigest: (0, identity_1.executionPlanDigest)(text),
|
|
491
|
+
manifest: raw, executionMode: executionModeFromValidatedText(text),
|
|
492
|
+
};
|
|
493
|
+
freezeJson(report);
|
|
494
|
+
verifiedValidReports.add(report);
|
|
495
|
+
verifiedSnapshots.set(report, text);
|
|
496
|
+
return report;
|
|
497
|
+
}
|
|
498
|
+
/** Read-only validator for a bounded compact-slices/v1 Markdown plan. */
|
|
499
|
+
function validatePlanFile(planPath, cwd = process.cwd()) {
|
|
500
|
+
return validatePlan(planPath, cwd);
|
|
501
|
+
}
|
|
502
|
+
/** Validate an already-authenticated plan snapshot without reopening planPath. */
|
|
503
|
+
function validatePlanSnapshot(planPath, cwd, text) {
|
|
504
|
+
if (typeof text !== 'string')
|
|
505
|
+
throw new Error('plan snapshot must be text');
|
|
506
|
+
return validatePlan(planPath, cwd, Buffer.from(text, 'utf8'));
|
|
393
507
|
}
|
|
@@ -17,6 +17,22 @@ describe('exec-wrapper', () => {
|
|
|
17
17
|
let dir;
|
|
18
18
|
beforeEach(() => { dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-wrap-')); });
|
|
19
19
|
afterEach(() => { fs_1.default.rmSync(dir, { recursive: true, force: true }); });
|
|
20
|
+
test.each(['../escape', '..\\escape', '/escape', 'C:\\escape', '', '.', '..', 'sentinel\n', 'x'.repeat(129)])('rejects unsafe sidecar components before writes (%#)', async (value) => {
|
|
21
|
+
for (const helper of [exec_wrapper_1.claimPath, exec_wrapper_1.identityPath, exec_wrapper_1.resultPath, exec_wrapper_1.logPath, exec_wrapper_1.replayVerdict]) {
|
|
22
|
+
expect(() => helper(dir, value, 'nonce')).toThrow(/sidecar/);
|
|
23
|
+
expect(() => helper(dir, 'job', value)).toThrow(/sidecar/);
|
|
24
|
+
}
|
|
25
|
+
await expect((0, exec_wrapper_1.runExecWrapper)({ logsRoot: path_1.default.join(dir, 'unused'), jobId: value, nonce: 'nonce', argv: ['node', '-e', 'process.exit(0)'], cwd: '.' })).rejects.toThrow(/sidecar/);
|
|
26
|
+
expect(fs_1.default.readdirSync(dir)).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
test.each([
|
|
29
|
+
{ argv: ['node'], cwd: '../escape' },
|
|
30
|
+
{ argv: [undefined], cwd: '.' },
|
|
31
|
+
{ argv: ['node', 'sentinel\u0000'], cwd: '.' },
|
|
32
|
+
])('rejects invalid execution inputs before sidecar writes (%#)', async (inputs) => {
|
|
33
|
+
await expect((0, exec_wrapper_1.runExecWrapper)({ logsRoot: path_1.default.join(dir, 'unused'), jobId: 'job', nonce: 'nonce', repoRoot: dir, ...inputs })).rejects.toThrow();
|
|
34
|
+
expect(fs_1.default.readdirSync(dir)).toEqual([]);
|
|
35
|
+
});
|
|
20
36
|
test('claim + identity sidecar + resultado terminal atomico (R1.8)', async () => {
|
|
21
37
|
const out = await (0, exec_wrapper_1.runExecWrapper)({ logsRoot: dir, jobId: 'job1', nonce: 'nonceA', argv: ['node', '-e', 'setTimeout(()=>process.exit(0), 300)'], cwd: '.' });
|
|
22
38
|
expect(out.exitCode).toBe(0);
|
|
@@ -13,6 +13,8 @@ const reconcile_1 = require("../../../src/commands/job/reconcile");
|
|
|
13
13
|
const reap_1 = require("../../../src/commands/job/reap");
|
|
14
14
|
const job_1 = require("../../../src/commands/job");
|
|
15
15
|
const watch_1 = require("../../../src/commands/watch");
|
|
16
|
+
const init_1 = require("../../../src/commands/watch/init");
|
|
17
|
+
const validate_1 = require("../../../src/core/plan/validate");
|
|
16
18
|
const types_1 = require("../../../src/core/journal/types");
|
|
17
19
|
const store_1 = require("../../../src/core/journal/store");
|
|
18
20
|
const descriptor_1 = require("../../../src/core/tracks/descriptor");
|
|
@@ -501,6 +503,16 @@ function trackRefFixture(overrides, worktreePath) {
|
|
|
501
503
|
function descriptorFixture(overrides, planRoot) {
|
|
502
504
|
return { schema: 1, planRoot, planBranch: 'main', trackId: 'cli', planJournalId: 'j-1', fencingToken: 'f'.repeat(32), ...overrides };
|
|
503
505
|
}
|
|
506
|
+
function initCompactJournal(repo, branch) {
|
|
507
|
+
const planPath = path_1.default.join(repo, 'plans', 'fixture.md');
|
|
508
|
+
fs_1.default.mkdirSync(path_1.default.dirname(planPath), { recursive: true });
|
|
509
|
+
fs_1.default.writeFileSync(planPath, fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v1/valid.md'), 'utf8'));
|
|
510
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'source.md'), '## Canonical source\nfixture source\n');
|
|
511
|
+
const report = (0, validate_1.validatePlanFile)('plans/fixture.md', repo);
|
|
512
|
+
if (report.state !== 'valid')
|
|
513
|
+
throw new Error(`compact fixture must validate: ${JSON.stringify(report)}`);
|
|
514
|
+
(0, init_1.initWatch)(repo, branch, { path: 'plans/fixture.md', report });
|
|
515
|
+
}
|
|
504
516
|
async function runCommand(register, argv, cwd) {
|
|
505
517
|
const prog = new commander_1.Command();
|
|
506
518
|
prog.exitOverride();
|
|
@@ -543,12 +555,12 @@ describe('guard de contexto en awm job / awm watch (R9.4)', () => {
|
|
|
543
555
|
gitInitAt(planRoot, 'main');
|
|
544
556
|
gitInitAt(trackRoot, 'track/cli');
|
|
545
557
|
gitInitAt(otherRoot, 'track/cli');
|
|
546
|
-
(
|
|
558
|
+
initCompactJournal(planRoot, 'main');
|
|
547
559
|
const planState = (0, store_1.readJournal)(planRoot, 'main').state;
|
|
548
560
|
planState.journalId = 'j-1';
|
|
549
561
|
planState.tracks = [trackRefFixture({ fencingToken: 'f'.repeat(32) }, trackRoot)];
|
|
550
562
|
(0, store_1.writeJournal)(planRoot, 'main', planState);
|
|
551
|
-
(
|
|
563
|
+
initCompactJournal(trackRoot, 'track/cli');
|
|
552
564
|
const trackState = (0, store_1.readJournal)(trackRoot, 'track/cli').state;
|
|
553
565
|
trackState.trackContext = { trackId: 'cli', taskIds: [], planDigest: 'x', baseSha: 'y', planJournalId: 'j-1' };
|
|
554
566
|
(0, store_1.writeJournal)(trackRoot, 'track/cli', trackState);
|
|
@@ -556,7 +568,7 @@ describe('guard de contexto en awm job / awm watch (R9.4)', () => {
|
|
|
556
568
|
// otherRoot: mismo descriptor (mismo plan/trackId/fencing) pero NO es
|
|
557
569
|
// el worktree que el TrackRef del plan declara — su realpath jamas
|
|
558
570
|
// puede autenticar (R9.4), aunque el resto del descriptor "calce".
|
|
559
|
-
(
|
|
571
|
+
initCompactJournal(otherRoot, 'track/cli');
|
|
560
572
|
const otherState = (0, store_1.readJournal)(otherRoot, 'track/cli').state;
|
|
561
573
|
otherState.trackContext = { trackId: 'cli', taskIds: [], planDigest: 'x', baseSha: 'y', planJournalId: 'j-1' };
|
|
562
574
|
(0, store_1.writeJournal)(otherRoot, 'track/cli', otherState);
|
|
@@ -580,24 +592,26 @@ describe('guard de contexto en awm job / awm watch (R9.4)', () => {
|
|
|
580
592
|
expect(exitCode).toBe(1);
|
|
581
593
|
expect(JSON.parse(out).pass).toBe(false);
|
|
582
594
|
});
|
|
583
|
-
test('`watch --init` desde un cwd SIN descriptor
|
|
595
|
+
test('`watch --init --plan` desde un cwd SIN descriptor crea un binding compacto', async () => {
|
|
584
596
|
const plainRepo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-guard-plain-'));
|
|
585
597
|
try {
|
|
586
598
|
gitInitAt(plainRepo, 'main');
|
|
587
|
-
const
|
|
599
|
+
const planPath = path_1.default.join(plainRepo, 'plans', 'fixture.md');
|
|
600
|
+
fs_1.default.mkdirSync(path_1.default.dirname(planPath), { recursive: true });
|
|
601
|
+
fs_1.default.writeFileSync(planPath, fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v1/valid.md'), 'utf8'));
|
|
602
|
+
fs_1.default.writeFileSync(path_1.default.join(plainRepo, 'source.md'), '## Canonical source\nfixture source\n');
|
|
603
|
+
const { out, err, exitCode } = await runWatch(['--init', '--plan', 'plans/fixture.md'], plainRepo);
|
|
588
604
|
expect(exitCode).toBeNull();
|
|
589
605
|
expect(err).toBe('');
|
|
590
606
|
expect(out).toContain('journal inicializado');
|
|
607
|
+
expect((0, store_1.readJournal)(plainRepo, 'main').state).toMatchObject({ schema: 2, planBinding: { path: 'plans/fixture.md', executionMode: 'desatendido' } });
|
|
591
608
|
}
|
|
592
609
|
finally {
|
|
593
610
|
fs_1.default.rmSync(plainRepo, { recursive: true, force: true });
|
|
594
611
|
}
|
|
595
612
|
});
|
|
596
|
-
test('`watch --init` desde el cwd autenticado
|
|
597
|
-
|
|
598
|
-
expect(err).not.toContain('cwd no autenticado');
|
|
599
|
-
expect(exitCode).toBeNull();
|
|
600
|
-
expect(out).toContain('journal inicializado');
|
|
613
|
+
test('`watch --init --plan` desde el cwd autenticado alcanza la guarda de lifecycle y rechaza sobrescribir el binding', async () => {
|
|
614
|
+
await expect(runWatch(['--init', '--plan', 'plans/fixture.md'], trackRoot)).rejects.toThrow(/overwrite|sobrescribir/i);
|
|
601
615
|
});
|
|
602
616
|
test('`watch --init` desde un cwd cuyo descriptor no autentica rechaza con "cwd no autenticado"', async () => {
|
|
603
617
|
const { err, exitCode } = await runWatch(['--init'], otherRoot);
|
|
@@ -7,30 +7,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
|
+
let tmpHome;
|
|
11
|
+
let originalHome;
|
|
12
|
+
let originalAwmHome;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pin-home-'));
|
|
15
|
+
originalHome = process.env.HOME;
|
|
16
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
17
|
+
process.env.HOME = tmpHome;
|
|
18
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
19
|
+
jest.resetModules();
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
23
|
+
if (originalHome === undefined)
|
|
24
|
+
delete process.env.HOME;
|
|
25
|
+
else
|
|
26
|
+
process.env.HOME = originalHome;
|
|
27
|
+
if (originalAwmHome === undefined)
|
|
28
|
+
delete process.env.AWM_HOME;
|
|
29
|
+
else
|
|
30
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
31
|
+
});
|
|
32
|
+
const readPrefs = () => JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), 'utf-8'));
|
|
10
33
|
describe('pin/unpin (editores de preferences)', () => {
|
|
11
|
-
let tmpHome;
|
|
12
|
-
let originalHome;
|
|
13
|
-
let originalAwmHome;
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pin-home-'));
|
|
16
|
-
originalHome = process.env.HOME;
|
|
17
|
-
originalAwmHome = process.env.AWM_HOME;
|
|
18
|
-
process.env.HOME = tmpHome;
|
|
19
|
-
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
20
|
-
jest.resetModules();
|
|
21
|
-
});
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
24
|
-
if (originalHome === undefined)
|
|
25
|
-
delete process.env.HOME;
|
|
26
|
-
else
|
|
27
|
-
process.env.HOME = originalHome;
|
|
28
|
-
if (originalAwmHome === undefined)
|
|
29
|
-
delete process.env.AWM_HOME;
|
|
30
|
-
else
|
|
31
|
-
process.env.AWM_HOME = originalAwmHome;
|
|
32
|
-
});
|
|
33
|
-
const readPrefs = () => JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), 'utf-8'));
|
|
34
34
|
it('setPin escribe pins.base normalizado (acepta prefijo v)', () => {
|
|
35
35
|
const { setPin } = require('../../src/commands/pin');
|
|
36
36
|
setPin('base', 'v1.2.0');
|