agentic-workflow-manager 9.6.1 → 9.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/src/commands/doctor.js +4 -2
- package/dist/src/commands/export.js +3 -0
- package/dist/src/commands/init.js +10 -2
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +7 -5
- package/dist/src/commands/registry/install-bundles.js +4 -4
- package/dist/src/commands/registry/status.js +4 -4
- package/dist/src/commands/sync.js +2 -1
- package/dist/src/commands/track/index.js +3 -7
- package/dist/src/commands/update.js +3 -1
- package/dist/src/commands/watch/apply.js +15 -0
- package/dist/src/commands/watch/tracks.js +56 -7
- package/dist/src/core/bundle-install.js +2 -2
- package/dist/src/core/bundles.js +92 -19
- package/dist/src/core/diagnostics/context.js +1 -1
- package/dist/src/core/export/index.js +1 -1
- package/dist/src/core/export/resolve.js +2 -2
- package/dist/src/core/journal/types.js +1 -0
- package/dist/src/core/reconciliation.js +1 -1
- package/dist/src/core/tracks/git.js +15 -3
- package/dist/src/core/tracks/protocol.js +26 -6
- package/dist/src/index.js +46 -39
- package/dist/src/utils/grouping.js +1 -1
- package/dist/src/utils/registry-view.js +1 -1
- package/dist/tests/commands/bundle-diagnostics.test.js +145 -0
- package/dist/tests/commands/export.test.js +28 -2
- package/dist/tests/commands/multi-agent-targeting.test.js +1 -1
- package/dist/tests/commands/track/verbs.test.js +6 -0
- package/dist/tests/commands/watch/apply.test.js +46 -9
- package/dist/tests/commands/watch/track-remove-request.test.js +165 -0
- package/dist/tests/commands/watch/track-teardown-crash.test.js +27 -0
- package/dist/tests/core/bundle-install.test.js +36 -0
- package/dist/tests/core/bundles-multiroot.test.js +24 -2
- package/dist/tests/core/bundles-overrides.test.js +10 -7
- package/dist/tests/core/bundles.test.js +75 -6
- package/dist/tests/core/diagnostics/context.test.js +2 -2
- package/dist/tests/core/export/engine.test.js +1 -1
- package/dist/tests/core/export/resolve.test.js +1 -1
- package/dist/tests/core/init/mutation-targets.test.js +1 -1
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/steps.test.js +1 -1
- package/dist/tests/core/journal/types.test.js +11 -0
- package/dist/tests/core/reconciliation.test.js +1 -1
- package/dist/tests/core/sync-profile-atomicity.test.js +1 -1
- package/dist/tests/core/tracks/git.test.js +7 -0
- package/dist/tests/core/tracks/protocol.test.js +103 -0
- package/dist/tests/structural/bundle-skill-reference-contract.test.js +56 -0
- package/dist/tests/utils/grouping.test.js +2 -2
- package/dist/tests/utils/registry-view.test.js +7 -7
- package/package.json +1 -1
- 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,165 @@
|
|
|
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
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const git_fixture_1 = require("../../helpers/git-fixture");
|
|
9
|
+
const tracks_1 = require("../../../src/commands/watch/tracks");
|
|
10
|
+
const apply_1 = require("../../../src/commands/watch/apply");
|
|
11
|
+
const emit_1 = require("../../../src/commands/track/emit");
|
|
12
|
+
const store_1 = require("../../../src/core/journal/store");
|
|
13
|
+
const paths_1 = require("../../../src/core/journal/paths");
|
|
14
|
+
const BRANCH = 'main';
|
|
15
|
+
describe('awm track remove -> fallback teardown', () => {
|
|
16
|
+
let planRoot;
|
|
17
|
+
let baseSha;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
planRoot = (0, git_fixture_1.initRepo)();
|
|
20
|
+
(0, git_fixture_1.commitFile)(planRoot, '.gitignore', '.awm/\n');
|
|
21
|
+
baseSha = (0, git_fixture_1.commitFile)(planRoot, 'seed.txt', 'seed');
|
|
22
|
+
(0, store_1.initJournal)(planRoot, BRANCH);
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => fs_1.default.rmSync(planRoot, { recursive: true, force: true }));
|
|
25
|
+
function declareCohort(cohortPhase = 'ACTIVE') {
|
|
26
|
+
const state = (0, store_1.readJournal)(planRoot, BRANCH).state;
|
|
27
|
+
state.cohortPhase = cohortPhase;
|
|
28
|
+
state.cohortBaseSha = baseSha;
|
|
29
|
+
state.tracks = ['a', 'b'].map((trackId) => ({
|
|
30
|
+
trackId, worktreePath: path_1.default.join(planRoot, `../nope-${trackId}`), branch: `awm-track/${trackId}`,
|
|
31
|
+
ownership: [], sharedResources: [], dependsOn: [], fencingToken: `fence-${trackId}`.padEnd(32, '0'),
|
|
32
|
+
phase: 'ACTIVE', readinessNonce: `ready-${trackId}`.padEnd(32, '0'),
|
|
33
|
+
}));
|
|
34
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, state);
|
|
35
|
+
return (0, store_1.readJournal)(planRoot, BRANCH).state;
|
|
36
|
+
}
|
|
37
|
+
function inertRuntime() {
|
|
38
|
+
const real = (0, tracks_1.defaultTrackRuntime)(planRoot, BRANCH);
|
|
39
|
+
return { ...real,
|
|
40
|
+
addWorktree: () => { throw new Error('no external mutation expected'); },
|
|
41
|
+
initTrackJournal: () => { throw new Error('no external mutation expected'); },
|
|
42
|
+
spawnSupervisor: () => { throw new Error('no external mutation expected'); },
|
|
43
|
+
stopOwnSupervisor: () => { throw new Error('no external mutation expected'); },
|
|
44
|
+
removeOwnedWorktree: () => { throw new Error('no external mutation expected'); },
|
|
45
|
+
removeOwnedBranch: () => { throw new Error('no external mutation expected'); },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function events() {
|
|
49
|
+
if (!fs_1.default.existsSync((0, paths_1.eventsPath)(planRoot, BRANCH)))
|
|
50
|
+
return [];
|
|
51
|
+
return fs_1.default.readFileSync((0, paths_1.eventsPath)(planRoot, BRANCH), 'utf8').trim().split('\n').filter(Boolean)
|
|
52
|
+
.map((line) => JSON.parse(line));
|
|
53
|
+
}
|
|
54
|
+
it('consume el marker antes que join y persiste fallback cohort-wide (TR-REQ-03, TR-REQ-04)', async () => {
|
|
55
|
+
const state = declareCohort();
|
|
56
|
+
state.tracks[0].joinRequested = true;
|
|
57
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, state);
|
|
58
|
+
(0, emit_1.emitTrackRequest)(planRoot, BRANCH, 'g1', 'track-teardown-request', 'b');
|
|
59
|
+
(0, apply_1.consumePendingRequests)(planRoot, BRANCH, 'g1');
|
|
60
|
+
const out = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, inertRuntime(), 2);
|
|
61
|
+
expect(out.state.cohortPhase).toBe('FALLBACK_PENDING');
|
|
62
|
+
// Consumir el marcador es su propia frontera durable: el teardown
|
|
63
|
+
// empieza recién en la siguiente llamada.
|
|
64
|
+
expect(out.effectExecuted).toBeNull();
|
|
65
|
+
expect(out.state.cohortFallbackReason).toBe('controller-requested:b');
|
|
66
|
+
expect(out.state.tracks?.find((track) => track.trackId === 'b')?.teardownRequested)
|
|
67
|
+
.toBeUndefined();
|
|
68
|
+
expect(events().some((event) => event.kind === 'track-teardown-observed' && event.trackId === 'b'))
|
|
69
|
+
.toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('un marker sobreviviente a restart converge por el mismo reducer (TR-REQ-07)', async () => {
|
|
72
|
+
const state = declareCohort();
|
|
73
|
+
state.tracks[0].teardownRequested = true;
|
|
74
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, state);
|
|
75
|
+
const out = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, inertRuntime(), 2);
|
|
76
|
+
expect(out.state.cohortPhase).toBe('FALLBACK_PENDING');
|
|
77
|
+
expect(out.state.tracks[0].teardownRequested).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
it.each(['SERIAL', 'COMPLETE'])('consume remove tardío como moot en %s (TR-REQ-09)', async (phase) => {
|
|
80
|
+
const state = declareCohort(phase);
|
|
81
|
+
for (const track of state.tracks) {
|
|
82
|
+
track.phase = phase === 'SERIAL' ? 'REMOVED' : 'JOINED';
|
|
83
|
+
track.frozenHeadSha = phase === 'COMPLETE' ? baseSha : undefined;
|
|
84
|
+
}
|
|
85
|
+
state.tracks[0].teardownRequested = true;
|
|
86
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, state);
|
|
87
|
+
const out = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, inertRuntime(), 2);
|
|
88
|
+
expect(out.state.cohortPhase).toBe(phase);
|
|
89
|
+
expect(out.state.tracks[0].teardownRequested).toBeUndefined();
|
|
90
|
+
expect(events()).toContainEqual(expect.objectContaining({ kind: 'track-teardown-moot', trackId: 'a', phase }));
|
|
91
|
+
});
|
|
92
|
+
it('una request en cohorte BLOCKED es moot y no reabre fallback', async () => {
|
|
93
|
+
const state = declareCohort('BLOCKED');
|
|
94
|
+
state.tracks[0].phase = 'BLOCKED';
|
|
95
|
+
state.tracks[1].phase = 'REMOVED';
|
|
96
|
+
state.tracks[1].teardownRequested = true;
|
|
97
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, state);
|
|
98
|
+
const out = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, inertRuntime(), 2);
|
|
99
|
+
expect(out.effectExecuted).toBeNull();
|
|
100
|
+
expect(out.state.cohortPhase).toBe('BLOCKED');
|
|
101
|
+
expect(out.state.tracks[1].teardownRequested).toBeUndefined();
|
|
102
|
+
expect(events()).toContainEqual(expect.objectContaining({ kind: 'track-teardown-moot', trackId: 'b', phase: 'BLOCKED' }));
|
|
103
|
+
});
|
|
104
|
+
it('consume exactamente un marcador de teardown por reconcile antes de begin-teardown (issue #93)', async () => {
|
|
105
|
+
const s = declareCohort('ACTIVE');
|
|
106
|
+
s.tracks.forEach((track) => { track.teardownRequested = true; });
|
|
107
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, s);
|
|
108
|
+
const first = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, inertRuntime(), 2);
|
|
109
|
+
expect(first.effectExecuted).toBeNull();
|
|
110
|
+
expect(first.state.tracks?.find((track) => track.trackId === 'a')?.teardownRequested).toBeUndefined();
|
|
111
|
+
expect(first.state.tracks?.find((track) => track.trackId === 'b')?.teardownRequested).toBe(true);
|
|
112
|
+
const second = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, first.state, inertRuntime(), 2);
|
|
113
|
+
expect(second.effectExecuted).toBeNull();
|
|
114
|
+
expect(second.state.tracks?.every((track) => track.teardownRequested === undefined)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
it('una request repetida durante teardown no rebobina fases ni ejecuta trabajo destructivo (issue #93)', async () => {
|
|
117
|
+
declareCohort('ACTIVE');
|
|
118
|
+
(0, emit_1.emitTrackRequest)(planRoot, BRANCH, 'g1', 'track-teardown-request', 'a');
|
|
119
|
+
(0, apply_1.consumePendingRequests)(planRoot, BRANCH, 'g1');
|
|
120
|
+
const afterFirst = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, inertRuntime(), 2);
|
|
121
|
+
(0, emit_1.emitTrackRequest)(planRoot, BRANCH, 'g2', 'track-teardown-request', 'a');
|
|
122
|
+
(0, apply_1.consumePendingRequests)(planRoot, BRANCH, 'g2');
|
|
123
|
+
const repeated = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, inertRuntime(), 2);
|
|
124
|
+
expect(afterFirst.state.tracks?.find((track) => track.trackId === 'a')?.phase).toBe('TEARDOWN_REQUESTED');
|
|
125
|
+
expect(repeated.effectExecuted).toBeNull();
|
|
126
|
+
expect(repeated.state.tracks?.find((track) => track.trackId === 'a')?.phase).toBe('TEARDOWN_REQUESTED');
|
|
127
|
+
expect(repeated.state.cohortPhase).toBe('FALLBACK_PENDING');
|
|
128
|
+
});
|
|
129
|
+
it('request durante FINAL_INTERLOCK persiste fallback sin evidencia final stale (issue #93)', async () => {
|
|
130
|
+
const s = declareCohort('FINAL_INTERLOCK');
|
|
131
|
+
for (const track of s.tracks)
|
|
132
|
+
track.phase = 'MERGED_UNVERIFIED';
|
|
133
|
+
for (const track of s.tracks)
|
|
134
|
+
track.frozenHeadSha = `${track.trackId}-head`;
|
|
135
|
+
s.globalQaHeadSha = 'qa-head';
|
|
136
|
+
s.finalIntegrationJobId = 'integration-job';
|
|
137
|
+
s.qaFinalizeRequested = { headSha: 'qa-head', at: new Date().toISOString() };
|
|
138
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, s);
|
|
139
|
+
(0, emit_1.emitTrackRequest)(planRoot, BRANCH, 'g1', 'track-teardown-request', 'a');
|
|
140
|
+
(0, apply_1.consumePendingRequests)(planRoot, BRANCH, 'g1');
|
|
141
|
+
let releases = 0;
|
|
142
|
+
const runtime = { ...inertRuntime(), releaseIntegrationLockIfHeld: () => { releases += 1; } };
|
|
143
|
+
const out = await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, runtime, 2);
|
|
144
|
+
const durable = (0, store_1.readJournal)(planRoot, BRANCH).state;
|
|
145
|
+
expect(out.state.cohortPhase).toBe('FALLBACK_PENDING');
|
|
146
|
+
expect(durable.globalQaHeadSha).toBeUndefined();
|
|
147
|
+
expect(durable.finalIntegrationJobId).toBeUndefined();
|
|
148
|
+
expect(durable.qaFinalizeRequested).toBeUndefined();
|
|
149
|
+
expect(durable.tracks?.every((track) => track.phase === 'TEARDOWN_REQUESTED')).toBe(true);
|
|
150
|
+
expect(releases).toBe(1);
|
|
151
|
+
});
|
|
152
|
+
it('un probe de branch indemostrable detiene teardown sin serializar (issue #93)', async () => {
|
|
153
|
+
const s = declareCohort('FALLBACK_PENDING');
|
|
154
|
+
s.tracks[0].phase = 'WORKTREE_REMOVED';
|
|
155
|
+
s.tracks[1].phase = 'REMOVED';
|
|
156
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, s);
|
|
157
|
+
const gitDir = path_1.default.join(planRoot, '.git');
|
|
158
|
+
fs_1.default.renameSync(gitDir, `${gitDir}.unavailable`);
|
|
159
|
+
await expect((0, tracks_1.reconcileTracks)(planRoot, BRANCH, s, (0, tracks_1.defaultTrackRuntime)(planRoot, BRANCH), 2)).rejects.toThrow(/indemostrable/);
|
|
160
|
+
fs_1.default.renameSync(`${gitDir}.unavailable`, gitDir);
|
|
161
|
+
const durable = (0, store_1.readJournal)(planRoot, BRANCH).state;
|
|
162
|
+
expect(durable.cohortPhase).toBe('FALLBACK_PENDING');
|
|
163
|
+
expect(durable.tracks?.find((track) => track.trackId === 'a')?.phase).toBe('WORKTREE_REMOVED');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -23,6 +23,8 @@ const child_process_1 = require("child_process");
|
|
|
23
23
|
const git_fixture_1 = require("../../helpers/git-fixture");
|
|
24
24
|
const tracks_1 = require("../../../src/commands/watch/tracks");
|
|
25
25
|
const store_1 = require("../../../src/core/journal/store");
|
|
26
|
+
const apply_1 = require("../../../src/commands/watch/apply");
|
|
27
|
+
const emit_1 = require("../../../src/commands/track/emit");
|
|
26
28
|
const git_1 = require("../../../src/core/tracks/git");
|
|
27
29
|
const descriptor_1 = require("../../../src/core/tracks/descriptor");
|
|
28
30
|
const BRANCH = 'main';
|
|
@@ -263,6 +265,31 @@ describe('reconcileTracks — crash/restart de teardown con git real (Task 13, R
|
|
|
263
265
|
expect(fs_1.default.existsSync(path_1.default.join(root, 'track-a'))).toBe(false);
|
|
264
266
|
expect(branchListed(planRoot, 'awm-track/a')).toBe(false);
|
|
265
267
|
});
|
|
268
|
+
test('request bridge sobrevive crash tras el marcador y converge a SERIAL sin repetir remociones (issue #93)', async () => {
|
|
269
|
+
let s = declareCohort(planRoot, root, baseSha);
|
|
270
|
+
materializeTrackA(s);
|
|
271
|
+
s = (0, store_1.readJournal)(planRoot, BRANCH).state;
|
|
272
|
+
s.cohortPhase = 'PREPARING';
|
|
273
|
+
s.tracks = s.tracks.map((track) => track.trackId === 'a'
|
|
274
|
+
? { ...track, phase: 'WORKTREE_CREATED', supervisorProcessRef: deadRef() }
|
|
275
|
+
: track);
|
|
276
|
+
(0, store_1.writeJournal)(planRoot, BRANCH, s);
|
|
277
|
+
(0, emit_1.emitTrackRequest)(planRoot, BRANCH, 'g1', 'track-teardown-request', 'a');
|
|
278
|
+
expect((0, apply_1.consumePendingRequests)(planRoot, BRANCH, 'g1').applied).toBe(1);
|
|
279
|
+
// Crash immediately after the request marker becomes the durable
|
|
280
|
+
// protocol observation: no teardown effect has run yet.
|
|
281
|
+
s = (await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, (0, store_1.readJournal)(planRoot, BRANCH).state, buildRuntime(planRoot, []), 2)).state;
|
|
282
|
+
expect(trackA(s).phase).toBe('TEARDOWN_REQUESTED');
|
|
283
|
+
const effects = [];
|
|
284
|
+
const restarted = buildRuntime(planRoot, effects);
|
|
285
|
+
for (let i = 0; i < 20 && s.cohortPhase !== 'SERIAL'; i++) {
|
|
286
|
+
s = (await (0, tracks_1.reconcileTracks)(planRoot, BRANCH, s, restarted, 2)).state;
|
|
287
|
+
}
|
|
288
|
+
expect(s.cohortPhase).toBe('SERIAL');
|
|
289
|
+
expect(trackA(s).phase).toBe('REMOVED');
|
|
290
|
+
expect(effects.filter((effect) => effect === 'worktree-effect:a')).toHaveLength(1);
|
|
291
|
+
expect(effects.filter((effect) => effect === 'branch-effect:awm-track/a')).toHaveLength(1);
|
|
292
|
+
});
|
|
266
293
|
// R4.6/R4.10/C9: un worktree cuyo ownership es indemostrable (descriptor
|
|
267
294
|
// ausente/corrupto, o simplemente no es el que `teardownIntent` espera)
|
|
268
295
|
// NUNCA se adopta ni se borra — el track se bloquea, y el contenido
|
|
@@ -41,6 +41,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
41
41
|
const path_1 = __importDefault(require("path"));
|
|
42
42
|
const bundles_1 = require("../../src/core/bundles");
|
|
43
43
|
const bundle_install_1 = require("../../src/core/bundle-install");
|
|
44
|
+
const providers_1 = require("../../src/providers");
|
|
44
45
|
const profile_1 = require("../../src/core/profile");
|
|
45
46
|
const executor_1 = require("../../src/core/executor");
|
|
46
47
|
const executor = __importStar(require("../../src/core/executor"));
|
|
@@ -139,6 +140,41 @@ function makeFixture() {
|
|
|
139
140
|
return { content, projectRoot, bundles: (0, bundles_1.discoverBundles)(content) };
|
|
140
141
|
}
|
|
141
142
|
describe('installBundle', () => {
|
|
143
|
+
it('expands canonical and legacy manifests into byte-identical artifacts for every agent target', () => {
|
|
144
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-binstall-compat-'));
|
|
145
|
+
const content = path_1.default.join(root, 'registry');
|
|
146
|
+
const writeManifests = (legacy) => {
|
|
147
|
+
const skills = (names) => legacy
|
|
148
|
+
? names.map((name, index) => ({ name, onSignal: index % 2 === 0 }))
|
|
149
|
+
: names;
|
|
150
|
+
fs_1.default.writeFileSync(path_1.default.join(content, 'bundles/base/bundle.json'), JSON.stringify({
|
|
151
|
+
name: 'base', scope: 'project', dependsOn: [],
|
|
152
|
+
skills: skills(['shared', 'base-only']), workflows: [], agents: [],
|
|
153
|
+
}));
|
|
154
|
+
fs_1.default.writeFileSync(path_1.default.join(content, 'bundles/extension/bundle.json'), JSON.stringify({
|
|
155
|
+
name: 'extension', scope: 'project', dependsOn: ['base'],
|
|
156
|
+
skills: skills(['shared', 'extension-only']), workflows: [], agents: [],
|
|
157
|
+
}));
|
|
158
|
+
};
|
|
159
|
+
for (const bundle of ['base', 'extension'])
|
|
160
|
+
fs_1.default.mkdirSync(path_1.default.join(content, 'bundles', bundle), { recursive: true });
|
|
161
|
+
fs_1.default.writeFileSync(path_1.default.join(content, 'catalog.json'), JSON.stringify({ bundles: [{ name: 'base', source: './bundles/base' }, { name: 'extension', source: './bundles/extension' }] }));
|
|
162
|
+
writeManifests(false);
|
|
163
|
+
const canonicalWarnings = [];
|
|
164
|
+
const legacyWarnings = [];
|
|
165
|
+
const canonicalReporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => canonicalWarnings.push(message));
|
|
166
|
+
const legacyReporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => legacyWarnings.push(message));
|
|
167
|
+
const canonicalBundles = (0, bundles_1.discoverBundles)(content, canonicalReporter);
|
|
168
|
+
writeManifests(true);
|
|
169
|
+
const legacyBundles = (0, bundles_1.discoverBundles)(content, legacyReporter);
|
|
170
|
+
for (const agent of providers_1.AGENT_TARGETS) {
|
|
171
|
+
const canonicalArtifacts = (0, bundle_install_1.expandBundleArtifacts)({ bundleName: 'extension', bundles: canonicalBundles, agents: [agent], method: 'copy', projectRoot: root, contentDir: content });
|
|
172
|
+
const legacyArtifacts = (0, bundle_install_1.expandBundleArtifacts)({ bundleName: 'extension', bundles: legacyBundles, agents: [agent], method: 'copy', projectRoot: root, contentDir: content });
|
|
173
|
+
expect(Buffer.from(JSON.stringify(canonicalArtifacts))).toEqual(Buffer.from(JSON.stringify(legacyArtifacts)));
|
|
174
|
+
}
|
|
175
|
+
expect(canonicalWarnings).toEqual([]);
|
|
176
|
+
expect(legacyWarnings).toHaveLength(2);
|
|
177
|
+
});
|
|
142
178
|
it('materializes the bundle closure as local symlinks (deps + own skills)', () => {
|
|
143
179
|
const { content, projectRoot, bundles } = makeFixture();
|
|
144
180
|
const result = (0, bundle_install_1.installBundle)({
|
|
@@ -8,9 +8,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
function writeBundleRoot(root, bundleName, skillName) {
|
|
11
|
+
const canonicalSkillName = typeof skillName === 'string' ? skillName : skillName.name;
|
|
11
12
|
fs_1.default.mkdirSync(path_1.default.join(root, 'bundles', bundleName), { recursive: true });
|
|
12
|
-
fs_1.default.mkdirSync(path_1.default.join(root, 'skills',
|
|
13
|
-
fs_1.default.writeFileSync(path_1.default.join(root, 'skills',
|
|
13
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'skills', canonicalSkillName), { recursive: true });
|
|
14
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'skills', canonicalSkillName, 'SKILL.md'), `---\nname: ${canonicalSkillName}\ndescription: d\n---\n`);
|
|
14
15
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles', bundleName, 'bundle.json'), JSON.stringify({ name: bundleName, version: '1.0.0', scope: 'ambient', skills: [skillName] }));
|
|
15
16
|
fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({
|
|
16
17
|
version: 1,
|
|
@@ -38,6 +39,27 @@ describe('bundles multi-root', () => {
|
|
|
38
39
|
expect(all.map((b) => b.name).sort()).toEqual(['dev-x', 'personal-x']);
|
|
39
40
|
expect(all.find((b) => b.name === 'personal-x').contentRoot).toBe(rootB);
|
|
40
41
|
});
|
|
42
|
+
it('composes one legacy migration diagnostic for each affected manifest', () => {
|
|
43
|
+
writeBundleRoot(rootA, 'dev-x', { name: 'sx', onSignal: true });
|
|
44
|
+
writeBundleRoot(rootB, 'personal-x', { name: 'px', onSignal: false });
|
|
45
|
+
const { inspectAllBundles } = require('../../src/core/bundles');
|
|
46
|
+
const result = inspectAllBundles([rootA, rootB]);
|
|
47
|
+
expect(result.bundles.map((b) => b.skills)).toEqual([['sx'], ['px']]);
|
|
48
|
+
expect(result.diagnostics).toHaveLength(2);
|
|
49
|
+
expect(new Set(result.diagnostics).size).toBe(2);
|
|
50
|
+
});
|
|
51
|
+
it('keeps diagnostics distinct when manifest identities share a truncated prefix', () => {
|
|
52
|
+
const sharedPrefix = path_1.default.join(tmp, ...Array.from({ length: 30 }, () => 'shared-prefix'));
|
|
53
|
+
const longRootA = path_1.default.join(sharedPrefix, 'registry-a');
|
|
54
|
+
const longRootB = path_1.default.join(sharedPrefix, 'registry-b');
|
|
55
|
+
writeBundleRoot(longRootA, 'legacy-a', { name: 'sa', onSignal: true });
|
|
56
|
+
writeBundleRoot(longRootB, 'legacy-b', { name: 'sb', onSignal: false });
|
|
57
|
+
const { inspectAllBundles } = require('../../src/core/bundles');
|
|
58
|
+
const result = inspectAllBundles([longRootA, longRootB]);
|
|
59
|
+
expect(result.diagnostics).toHaveLength(2);
|
|
60
|
+
expect(result.diagnostics.every((diagnostic) => diagnostic.length <= 512)).toBe(true);
|
|
61
|
+
expect(result.diagnostics[0]).not.toBe(result.diagnostics[1]);
|
|
62
|
+
});
|
|
41
63
|
it('discoverAllBundles throws naming both sources on bundle name collision', () => {
|
|
42
64
|
writeBundleRoot(rootA, 'dup', 's1');
|
|
43
65
|
writeBundleRoot(rootB, 'dup', 's2');
|
|
@@ -7,9 +7,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
function writeBundleRoot(root, bundleName, skillName) {
|
|
10
|
+
const canonicalSkillName = typeof skillName === 'string' ? skillName : skillName.name;
|
|
10
11
|
fs_1.default.mkdirSync(path_1.default.join(root, 'bundles', bundleName), { recursive: true });
|
|
11
|
-
fs_1.default.mkdirSync(path_1.default.join(root, 'skills',
|
|
12
|
-
fs_1.default.writeFileSync(path_1.default.join(root, 'skills',
|
|
12
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'skills', canonicalSkillName), { recursive: true });
|
|
13
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'skills', canonicalSkillName, 'SKILL.md'), `---\nname: ${canonicalSkillName}\ndescription: d\n---\n`);
|
|
13
14
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles', bundleName, 'bundle.json'), JSON.stringify({ name: bundleName, version: '1.0.0', scope: 'ambient', skills: [skillName] }));
|
|
14
15
|
fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({
|
|
15
16
|
version: 1,
|
|
@@ -33,11 +34,13 @@ describe('bundle override resolution', () => {
|
|
|
33
34
|
writeBundleRoot(rootA, 'pack', 's1');
|
|
34
35
|
writeBundleRoot(rootB, 'pack', 's2');
|
|
35
36
|
fs_1.default.writeFileSync(path_1.default.join(rootB, 'awm-registry.json'), JSON.stringify({ overrides: ['pack'] }));
|
|
36
|
-
const {
|
|
37
|
-
const
|
|
38
|
-
expect(
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
37
|
+
const { inspectAllBundles } = require('../../src/core/bundles');
|
|
38
|
+
const result = inspectAllBundles([rootA, rootB]);
|
|
39
|
+
expect(result.bundles).toHaveLength(1);
|
|
40
|
+
expect(result.bundles[0].contentRoot).toBe(rootB);
|
|
41
|
+
expect(result.bundles[0].overrode).toBe(rootA);
|
|
42
|
+
expect(result.bundles[0].skills).toEqual(['s2']);
|
|
43
|
+
expect(result.diagnostics).toHaveLength(0);
|
|
41
44
|
});
|
|
42
45
|
it('undeclared collision still throws naming both sources', () => {
|
|
43
46
|
writeBundleRoot(rootA, 'dup', 's1');
|
|
@@ -21,7 +21,7 @@ function makeFixture() {
|
|
|
21
21
|
}));
|
|
22
22
|
fs_1.default.writeFileSync(path_1.default.join(content, 'bundles', 'dev', 'bundle.json'), JSON.stringify({
|
|
23
23
|
name: 'dev', version: '1.0.0', description: 'Dev core', scope: 'baseline', dependsOn: [],
|
|
24
|
-
skills: ['brainstorming',
|
|
24
|
+
skills: ['brainstorming', 'architecture-advisor'],
|
|
25
25
|
workflows: ['development-process'], agents: ['development-process'],
|
|
26
26
|
}));
|
|
27
27
|
fs_1.default.writeFileSync(path_1.default.join(content, 'bundles', 'frontend', 'bundle.json'), JSON.stringify({
|
|
@@ -47,17 +47,86 @@ describe('readCatalog', () => {
|
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
describe('discoverBundles', () => {
|
|
50
|
-
it('loads each bundle
|
|
50
|
+
it('loads each bundle with canonical skill names', () => {
|
|
51
51
|
const content = makeFixture();
|
|
52
52
|
const bundles = (0, bundles_1.discoverBundles)(content);
|
|
53
53
|
const dev = bundles.find((b) => b.name === 'dev');
|
|
54
|
-
expect(dev.skills).toEqual([
|
|
55
|
-
{ name: 'brainstorming', onSignal: false },
|
|
56
|
-
{ name: 'architecture-advisor', onSignal: true },
|
|
57
|
-
]);
|
|
54
|
+
expect(dev.skills).toEqual(['brainstorming', 'architecture-advisor']);
|
|
58
55
|
expect(dev.scope).toBe('baseline');
|
|
59
56
|
expect(dev.dependsOn).toEqual([]);
|
|
60
57
|
});
|
|
58
|
+
it('canonicalizes legacy skill objects and reports one bounded migration diagnostic per manifest', () => {
|
|
59
|
+
const content = makeFixture();
|
|
60
|
+
const manifest = path_1.default.join(content, 'bundles', 'dev', 'bundle.json');
|
|
61
|
+
fs_1.default.writeFileSync(manifest, JSON.stringify({
|
|
62
|
+
name: 'dev', version: '1.0.0', scope: 'baseline',
|
|
63
|
+
skills: [
|
|
64
|
+
{ name: 'brainstorming', onSignal: true },
|
|
65
|
+
{ name: 'architecture-advisor', onSignal: false },
|
|
66
|
+
],
|
|
67
|
+
}));
|
|
68
|
+
const { inspectBundles } = require('../../src/core/bundles');
|
|
69
|
+
const result = inspectBundles(content);
|
|
70
|
+
expect(result.bundles[0].skills).toEqual(['brainstorming', 'architecture-advisor']);
|
|
71
|
+
expect(result.diagnostics).toHaveLength(1);
|
|
72
|
+
expect(result.diagnostics[0]).toContain('bundle.json');
|
|
73
|
+
expect(result.diagnostics[0]).toContain('skills: ["skill-name"]');
|
|
74
|
+
expect(result.diagnostics[0]).toMatch(/v10/i);
|
|
75
|
+
expect(result.diagnostics[0]).not.toMatch(/[\x00-\x1f\x7f-\x9f]/);
|
|
76
|
+
expect(result.diagnostics[0]).not.toContain('{"name"');
|
|
77
|
+
expect(result.diagnostics[0].length).toBeLessThanOrEqual(512);
|
|
78
|
+
});
|
|
79
|
+
it('forwards one deduplicated legacy diagnostic to an injected reporter', () => {
|
|
80
|
+
const content = makeFixture();
|
|
81
|
+
const manifest = path_1.default.join(content, 'bundles', 'dev', 'bundle.json');
|
|
82
|
+
fs_1.default.writeFileSync(manifest, JSON.stringify({
|
|
83
|
+
name: 'dev', version: '1.0.0', scope: 'baseline',
|
|
84
|
+
skills: [{ name: 'brainstorming', onSignal: true }, { name: 'architecture-advisor', onSignal: false }],
|
|
85
|
+
}));
|
|
86
|
+
const diagnostics = [];
|
|
87
|
+
const bundles = (0, bundles_1.discoverBundles)(content, (diagnostic) => diagnostics.push(diagnostic));
|
|
88
|
+
expect(bundles[0].skills).toEqual(['brainstorming', 'architecture-advisor']);
|
|
89
|
+
expect(diagnostics).toHaveLength(1);
|
|
90
|
+
});
|
|
91
|
+
it('creates a command-scoped warning reporter that suppresses repeated diagnostics', () => {
|
|
92
|
+
const warnings = [];
|
|
93
|
+
const report = (0, bundles_1.createBundleDiagnosticReporter)((warning) => warnings.push(warning));
|
|
94
|
+
report('legacy warning');
|
|
95
|
+
report('legacy warning');
|
|
96
|
+
expect(warnings).toEqual(['warning: legacy warning']);
|
|
97
|
+
});
|
|
98
|
+
it('sanitizes, bounds, and then deduplicates caller-provided diagnostics', () => {
|
|
99
|
+
const warnings = [];
|
|
100
|
+
const report = (0, bundles_1.createBundleDiagnosticReporter)((warning) => warnings.push(warning));
|
|
101
|
+
const payload = `legacy\x1b\n${'x'.repeat(600)}`;
|
|
102
|
+
report(payload);
|
|
103
|
+
report(`legacy ${'x'.repeat(600)}`);
|
|
104
|
+
expect(warnings).toHaveLength(1);
|
|
105
|
+
expect(warnings[0]).toMatch(/^warning: legacy /);
|
|
106
|
+
expect(warnings[0]).not.toMatch(/[\x00-\x1f\x7f-\x9f]/);
|
|
107
|
+
expect(warnings[0].length).toBeLessThanOrEqual('warning: '.length + 512);
|
|
108
|
+
});
|
|
109
|
+
it.each([
|
|
110
|
+
['missing skills', undefined, /skills/],
|
|
111
|
+
['a non-array skills value', 'brainstorming', /skills/],
|
|
112
|
+
['an empty string skill', [''], /skills\[0\]/],
|
|
113
|
+
['an array skill', [['brainstorming']], /skills\[0\]/],
|
|
114
|
+
['a null skill', [null], /skills\[0\]/],
|
|
115
|
+
['an object missing name', [{ onSignal: true }], /skills\[0\]/],
|
|
116
|
+
['an object with an empty name', [{ name: '', onSignal: true }], /skills\[0\]/],
|
|
117
|
+
['an object with a non-string name', [{ name: 1, onSignal: true }], /skills\[0\]/],
|
|
118
|
+
['an object with a non-boolean onSignal', [{ name: 'brainstorming', onSignal: 'true' }], /skills\[0\]/],
|
|
119
|
+
['an object with an unknown key', [{ name: 'brainstorming', extra: true }], /skills\[0\]/],
|
|
120
|
+
])('rejects %s with a safe manifest identity', (_case, skills, location) => {
|
|
121
|
+
const content = makeFixture();
|
|
122
|
+
const manifest = path_1.default.join(content, 'bundles', 'dev', 'bundle.json');
|
|
123
|
+
const bundle = { name: 'dev', version: '1.0.0', scope: 'baseline' };
|
|
124
|
+
if (skills !== undefined)
|
|
125
|
+
bundle.skills = skills;
|
|
126
|
+
fs_1.default.writeFileSync(manifest, JSON.stringify(bundle));
|
|
127
|
+
expect(() => (0, bundles_1.discoverBundles)(content)).toThrow(/bundle\.json/);
|
|
128
|
+
expect(() => (0, bundles_1.discoverBundles)(content)).toThrow(location);
|
|
129
|
+
});
|
|
61
130
|
it('returns [] when catalog is missing', () => {
|
|
62
131
|
const empty = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-'));
|
|
63
132
|
expect((0, bundles_1.discoverBundles)(empty)).toEqual([]);
|
|
@@ -9,7 +9,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
function bundle(name, scope, skills) {
|
|
10
10
|
return {
|
|
11
11
|
name, description: '', version: '1.0.0', scope, visibility: 'public',
|
|
12
|
-
dependsOn: [], skills
|
|
12
|
+
dependsOn: [], skills,
|
|
13
13
|
workflows: [], agents: [],
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -86,7 +86,7 @@ describe('gatherContext', () => {
|
|
|
86
86
|
it('machine: devCore surfaces a missing per-agent artifact even when the shared skill link is already present', () => {
|
|
87
87
|
const bundleWithAgent = () => ({
|
|
88
88
|
name: 'dev-core', description: '', version: '1.0.0', scope: 'baseline', visibility: 'public',
|
|
89
|
-
dependsOn: [], skills: [
|
|
89
|
+
dependsOn: [], skills: ['development-process'],
|
|
90
90
|
workflows: [], agents: ['development-process'],
|
|
91
91
|
});
|
|
92
92
|
// Simulate OpenCode having already linked the shared ~/.agents/skills dir.
|
|
@@ -21,7 +21,7 @@ function makeRoot() {
|
|
|
21
21
|
}));
|
|
22
22
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles/dev/bundle.json'), JSON.stringify({
|
|
23
23
|
name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
|
|
24
|
-
skills: ['proc-skill',
|
|
24
|
+
skills: ['proc-skill', 'mermaid', 'ported'],
|
|
25
25
|
workflows: [], agents: [],
|
|
26
26
|
}));
|
|
27
27
|
const mk = (name, fm) => {
|
|
@@ -17,7 +17,7 @@ function makeRoot() {
|
|
|
17
17
|
}));
|
|
18
18
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles/dev/bundle.json'), JSON.stringify({
|
|
19
19
|
name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
|
|
20
|
-
skills: ['proc-skill',
|
|
20
|
+
skills: ['proc-skill', 'mermaid', 'ported'],
|
|
21
21
|
workflows: [], agents: [],
|
|
22
22
|
}));
|
|
23
23
|
const mk = (name, fm) => {
|
|
@@ -23,7 +23,7 @@ const tmp_1 = require("../../support/tmp");
|
|
|
23
23
|
function bundle(name, scope, skills) {
|
|
24
24
|
return {
|
|
25
25
|
name, description: '', version: '1.0.0', scope, visibility: 'public',
|
|
26
|
-
dependsOn: [], skills
|
|
26
|
+
dependsOn: [], skills,
|
|
27
27
|
workflows: [], agents: [],
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -31,7 +31,7 @@ function seedRegistry(contentRoot) {
|
|
|
31
31
|
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'bundles', 'dev'), { recursive: true });
|
|
32
32
|
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'bundles', 'dev', 'bundle.json'), JSON.stringify({
|
|
33
33
|
name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
|
|
34
|
-
skills: [
|
|
34
|
+
skills: ['brainstorming', 'using-awm'], workflows: [], agents: [],
|
|
35
35
|
}));
|
|
36
36
|
// Seed .git in the content root so gatherMachine.registryCache.present is true.
|
|
37
37
|
fs_1.default.mkdirSync(path_1.default.join(contentRoot, '.git'), { recursive: true });
|
|
@@ -12,7 +12,7 @@ const context_1 = require("../../../src/core/diagnostics/context");
|
|
|
12
12
|
function bundle(name, scope, skills) {
|
|
13
13
|
return {
|
|
14
14
|
name, description: '', version: '1.0.0', scope, visibility: 'public',
|
|
15
|
-
dependsOn: [], skills
|
|
15
|
+
dependsOn: [], skills,
|
|
16
16
|
workflows: [], agents: [],
|
|
17
17
|
};
|
|
18
18
|
}
|
|
@@ -148,6 +148,17 @@ describe('journal types', () => {
|
|
|
148
148
|
state.tracks[0].supervisorIntent.nonce = '';
|
|
149
149
|
expect((0, types_1.isWellFormedState)(state)).toBe(false);
|
|
150
150
|
});
|
|
151
|
+
test('TrackRef acepta teardownRequested booleano y rechaza otro runtime shape', () => {
|
|
152
|
+
const state = (0, types_1.emptyState)('main');
|
|
153
|
+
state.tracks = [{
|
|
154
|
+
trackId: 'cli', worktreePath: '/tmp/wt', branch: 'awm-track/cli', ownership: ['cli/'],
|
|
155
|
+
sharedResources: [], dependsOn: [], fencingToken: 'f'.repeat(32), phase: 'ACTIVE',
|
|
156
|
+
readinessNonce: 'r'.repeat(32), teardownRequested: true,
|
|
157
|
+
}];
|
|
158
|
+
expect((0, types_1.isWellFormedState)(state)).toBe(true);
|
|
159
|
+
state.tracks[0].teardownRequested = 'true';
|
|
160
|
+
expect((0, types_1.isWellFormedState)(state)).toBe(false);
|
|
161
|
+
});
|
|
151
162
|
test('trackContext, cohortPhase y trackIntegration validan shape cuando presentes (R9.1, R9.3)', () => {
|
|
152
163
|
const state = (0, types_1.emptyState)('main');
|
|
153
164
|
state.trackContext = { trackId: 'cli', taskIds: ['t1'], planDigest: 'd', baseSha: 'abc', planJournalId: 'j-plan' };
|
|
@@ -17,7 +17,7 @@ function writeBundle(contentRoot, name, scope, skills, dependsOn = []) {
|
|
|
17
17
|
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'bundles', name), { recursive: true });
|
|
18
18
|
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'bundles', name, 'bundle.json'), JSON.stringify({
|
|
19
19
|
name, version: '1.0.0', scope, dependsOn,
|
|
20
|
-
skills
|
|
20
|
+
skills, workflows: [], agents: [],
|
|
21
21
|
}));
|
|
22
22
|
for (const skill of skills) {
|
|
23
23
|
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'skills', skill), { recursive: true });
|
|
@@ -27,7 +27,7 @@ const bundle_install_1 = require("../../src/core/bundle-install");
|
|
|
27
27
|
function bundle(name, skills, contentRoot) {
|
|
28
28
|
return {
|
|
29
29
|
name, description: '', version: '1.0.0', scope: 'project', visibility: 'public',
|
|
30
|
-
dependsOn: [], skills
|
|
30
|
+
dependsOn: [], skills, workflows: [], agents: [],
|
|
31
31
|
source: `bundles/${name}`, contentRoot,
|
|
32
32
|
};
|
|
33
33
|
}
|
|
@@ -128,5 +128,12 @@ describe('git adapter', () => {
|
|
|
128
128
|
(0, child_process_1.execFileSync)('git', ['branch', 'awm-track/x'], { cwd: repo });
|
|
129
129
|
expect((0, git_1.branchExists)(repo, 'awm-track/x')).toBe(true);
|
|
130
130
|
});
|
|
131
|
+
test('branchExists falla cerrado cuando git no puede probar la ausencia', () => {
|
|
132
|
+
repo = (0, git_fixture_1.initRepo)();
|
|
133
|
+
(0, git_fixture_1.commitFile)(repo, 'a.txt', 'x');
|
|
134
|
+
const gitDir = path_1.default.join(repo, '.git');
|
|
135
|
+
fs_1.default.renameSync(gitDir, `${gitDir}.unavailable`);
|
|
136
|
+
expect(() => (0, git_1.branchExists)(repo, 'awm-track/nope')).toThrow(/indemostrable/);
|
|
137
|
+
});
|
|
131
138
|
});
|
|
132
139
|
});
|