test(#3339): fold the state/phase/dispatch & model-profile issue-* cluster — Wave 7
Folds 9 legacy issue-*.test.cjs regression files (140 test() blocks) into their module's main suite, per H3 (#3315) of the test-hygiene epic (#3053). LAST of 4 issue-* waves — closes out the 74-file fix-*/issue-* backlog (pending BUG_FILE_RE extension, held for a follow-up commit until Wave 6 is confirmed merged, per the epic's own zero-backlog precondition). - issue-2828-flat-roadmap-total-phases.test.cjs (1) + issue-3204-state- writer-phase-count.test.cjs (21): both target state-document.cjs buildStateFrontmatter via different CLI entrypoints — merged jointly into state-document.test.cjs, 0 dropped. - issue-2945-phase-complete-checkbox-rollback.test.cjs (4) + issue-2949- phase-complete-stage3-sentinel.test.cjs (4): both target phase.cts cmdPhaseComplete; issue explicitly warned of overlap — verified disjoint fixtures/assertions, 0 dropped, merged into phase.test.cjs. - issue-2927-reviewer-lane-overlay-invocation.test.cjs (10) merged into review-lane-descriptor.test.cjs. - issue-2939-dispatch-flatten-maxdepth.test.cjs (9) merged into host-integration.test.cjs, 2 dropped as verified exact duplicates. - issue-2977-frontmatter-bom.test.cjs (5) merged into frontmatter.test.cjs. - issue-2045-third-party-skills-surface.test.cjs (6) merged into capability-loader.test.cjs. - issue-2517-runtime-aware-profiles.test.cjs (80, the largest single fold in the epic) merged into model-resolver.test.cjs, 1 dropped as a verified true duplicate (checked against src/model-resolver.cts logic, not just title similarity). Fixed a genuine eslint irregular-whitespace finding: a literal BOM character embedded in a doc comment (pre-existing content from the original #2977 source, illustrating what a BOM looks like) — replaced with a readable U+FEFF notation. 3 stale doc references found and fixed (docs/adr/2313, 3180, 443). Zero net test-coverage loss. No production code changed.
This commit is contained in:
@@ -10491,3 +10491,366 @@ describe('#2572: phase complete warns when a SUMMARY claims files that never lan
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/issue-2945-phase-complete-checkbox-rollback.test.cjs — H3 Wave 7 test-hygiene sweep (#3339)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe('folded:issue-2945-phase-complete-checkbox-rollback (#3339)', () => {
|
||||
'use strict';
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
|
||||
/**
|
||||
* Regression test for #2945 — `phase complete`'s inline REQUIREMENTS.md checkbox
|
||||
* flip is traceability-blind: it flips `- [ ] **REQ-ID**` → `- [x]` unconditionally,
|
||||
* then attempts the traceability-row write, and KEEPS the flip when the row exists
|
||||
* but rejects the write (Out/Deferred/Blocked). The sibling `requirements.mark-complete`
|
||||
* got the #2788 defect-2 rollback; `cmdPhaseComplete`'s inline copy did not.
|
||||
*
|
||||
* The fix ports the rollback from `cmdRequirementsMarkComplete` (src/milestone.cts):
|
||||
* capture beforeCheckbox, track whether the row write actually changed (tableHit), and
|
||||
* restore beforeCheckbox when the row EXISTS but rejects the write.
|
||||
*
|
||||
* Matrix: .gsd/bug/fix/2945-phase-complete-checkbox-rollback/50-test-matrix.md
|
||||
*/
|
||||
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
|
||||
/**
|
||||
* Write a passed-VERIFICATION marker for the phase, then run `phase complete N`.
|
||||
* Mirrors phase.test.cjs's writePassedVerificationForPhase: a `<phase>-VERIFICATION.md`
|
||||
* with `status: passed` frontmatter. Requires the phase directory to exist.
|
||||
*/
|
||||
function runVerifiedPhaseComplete(args, tmpDir) {
|
||||
const argv = Array.isArray(args) ? args : args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
||||
const completeIdx = argv.findIndex((t, i) => t === 'complete' && argv[i - 1] === 'phase');
|
||||
const phase = argv[completeIdx + 1];
|
||||
const phasesDir = path.join(tmpDir, '.planning', 'phases');
|
||||
const wanted = parseInt(String(phase).replace(/^0+/, ''), 10);
|
||||
const phaseDirName = fs.readdirSync(phasesDir).find((name) => {
|
||||
const m = name.match(/^(\d+)/);
|
||||
return m && parseInt(m[1], 10) === wanted;
|
||||
});
|
||||
if (!phaseDirName) throw new Error(`no phase directory for phase ${phase}`);
|
||||
fs.writeFileSync(
|
||||
path.join(phasesDir, phaseDirName, `${phase}-VERIFICATION.md`),
|
||||
['---', 'status: passed', '---', '', '# Verification', ''].join('\n'),
|
||||
);
|
||||
return runGsdTools(args, tmpDir);
|
||||
}
|
||||
|
||||
describe('phase complete checkbox rollback (#2945)', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject('gsd-2945-');
|
||||
});
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
/**
|
||||
* Scaffold a single-phase project whose ROADMAP cites the given REQ-IDs, with a
|
||||
* REQUIREMENTS.md whose traceability table rows carry the given statuses, then run
|
||||
* `phase complete 1`. Returns the REQUIREMENTS.md content after completion.
|
||||
*/
|
||||
function completeWithRows(reqIds, rowStatuses) {
|
||||
const reqList = reqIds.join(', ');
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n- [ ] Phase 1: The phase\n\n### Phase 1: The phase\n**Goal:** do it\n**Requirements:** ${reqList}\n**Plans:** 1 plans\n`,
|
||||
);
|
||||
const reqLines = reqIds.map((id) => `- [ ] **${id}**: a requirement`).join('\n');
|
||||
const tableRows = reqIds.map((id, i) => `| ${id} | Phase 1 | ${rowStatuses[i]} |`).join('\n');
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'REQUIREMENTS.md'),
|
||||
`# Requirements\n\n## v1 Requirements\n\n${reqLines}\n\n## Traceability\n\n| Requirement | Phase | Status |\n|-------------|-------|--------|\n${tableRows}\n`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 01\n**Current Phase Name:** The phase\n**Status:** In progress\n**Current Plan:** 01-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`,
|
||||
);
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-the-phase');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
||||
|
||||
const result = runVerifiedPhaseComplete('phase complete 1', tmpDir);
|
||||
assert.ok(result.success, `phase complete failed: ${result.error || result.output}`);
|
||||
return fs.readFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), 'utf-8');
|
||||
}
|
||||
|
||||
test('deferredRowRollsBackCheckbox', () => {
|
||||
// Row 1 (failing-first regression): a Deferred row rejects the Status write, so the
|
||||
// checkbox must NOT flip (it stays [ ]), and the row must stay Deferred.
|
||||
const req = completeWithRows(['DEF-01'], ['Deferred']);
|
||||
assert.ok(req.includes('- [ ] **DEF-01**'), 'checkbox must stay [ ] when the row is Deferred (no silent divergence)');
|
||||
assert.ok(/^\| DEF-01 \| Phase 1 \| Deferred \|/m.test(req), 'row must stay Deferred');
|
||||
assert.ok(!req.includes('- [x] **DEF-01**'), 'checkbox must NOT have flipped to [x]');
|
||||
});
|
||||
|
||||
test('blockedRowRollsBackCheckbox', () => {
|
||||
// Row 2: an Out/Blocked row likewise rejects the write → checkbox stays [ ].
|
||||
const req = completeWithRows(['BLK-01'], ['Blocked']);
|
||||
assert.ok(req.includes('- [ ] **BLK-01**'), 'checkbox must stay [ ] when the row is Blocked');
|
||||
assert.ok(/^\| BLK-01 \| Phase 1 \| Blocked \|/m.test(req), 'row must stay Blocked');
|
||||
assert.ok(!req.includes('- [x] **BLK-01**'), 'checkbox must NOT have flipped to [x]');
|
||||
});
|
||||
|
||||
test('pendingRowStillFlipsAndAdvances', () => {
|
||||
// Row 3 (negative-space / unchanged forward behavior): a Pending row accepts the write,
|
||||
// so the checkbox MUST flip to [x] and the row MUST advance to Complete. An over-broad
|
||||
// rollback would break this.
|
||||
const req = completeWithRows(['FWD-01'], ['Pending']);
|
||||
assert.ok(req.includes('- [x] **FWD-01**'), 'checkbox MUST flip to [x] for a Pending (forward) row');
|
||||
assert.ok(/^\| FWD-01 \| Phase 1 \| Complete \|/m.test(req), 'row MUST advance to Complete');
|
||||
});
|
||||
|
||||
test('noRowStillFlipsCheckbox', () => {
|
||||
// Row 4 (acceptance #3): a cited REQ-ID with NO traceability row → checkbox still flips
|
||||
// (nothing to disagree with). The rollback only fires when a row EXISTS and rejects.
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n- [ ] Phase 1: The phase\n\n### Phase 1: The phase\n**Goal:** do it\n**Requirements:** NOROW-01\n**Plans:** 1 plans\n`,
|
||||
);
|
||||
// REQUIREMENTS.md with the checkbox but NO traceability table.
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'REQUIREMENTS.md'),
|
||||
`# Requirements\n\n## v1 Requirements\n\n- [ ] **NOROW-01**: a requirement with no traceability row\n`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 01\n**Current Phase Name:** The phase\n**Status:** In progress\n**Current Plan:** 01-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`,
|
||||
);
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-the-phase');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
||||
|
||||
const result = runVerifiedPhaseComplete('phase complete 1', tmpDir);
|
||||
assert.ok(result.success, `phase complete failed: ${result.error || result.output}`);
|
||||
const req = fs.readFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), 'utf-8');
|
||||
assert.ok(req.includes('- [x] **NOROW-01**'), 'checkbox MUST flip to [x] when no traceability row exists');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/issue-2949-phase-complete-stage3-sentinel.test.cjs — H3 Wave 7 test-hygiene sweep (#3339)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe('folded:issue-2949-phase-complete-stage3-sentinel (#3339)', () => {
|
||||
'use strict';
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
|
||||
/**
|
||||
* Regression test for #2949 — `phase complete`'s stage-3 lowest-outstanding-override
|
||||
* loop admits unchecked `0.x` backlog sentinel rows as `next_phase`, corrupting STATE.md
|
||||
* and desyncing `current_phase` from `current_phase_name`.
|
||||
*
|
||||
* Root cause: `src/phase.cts` stage-3 condition `!isChecked && comparePhaseNum(cbm[2], phaseNum) < 0`
|
||||
* has no sentinel filter, so `comparePhaseNum("0.1","12") === -12` admits the `0.x` backlog row.
|
||||
* The fix adds `&& !isSentinelPhaseId(cbm[2])` (reusing the existing zero-caller predicate), which
|
||||
* excludes both sentinel ranges (0 and 999). Stage-3 only here — PR #2815 (in-flight) covers
|
||||
* stages 1-2 for #2786.
|
||||
*
|
||||
* Matrix: .gsd/bug/fix/2949-phase-complete-stage3-sentinel-filter/50-test-matrix.md
|
||||
*/
|
||||
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
|
||||
/**
|
||||
* Write a passed-verification marker for a phase, then run `phase complete N`.
|
||||
* Mirrors phase.test.cjs's writePassedVerificationForPhase: a `<phase>-VERIFICATION.md`
|
||||
* with `status: passed` frontmatter, plus a SUMMARY for each plan (the completion gate
|
||||
* requires executed plans). Requires the phase directory to already exist.
|
||||
*/
|
||||
function runVerifiedPhaseComplete(args, tmpDir) {
|
||||
const argv = Array.isArray(args) ? args : args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
||||
const completeIdx = argv.findIndex((t, i) => t === 'complete' && argv[i - 1] === 'phase');
|
||||
const phase = argv[completeIdx + 1];
|
||||
const phasesDir = path.join(tmpDir, '.planning', 'phases');
|
||||
// Find the phase directory whose leading token matches the requested phase number.
|
||||
const wantedPadded = String(phase).replace(/^0+/, '');
|
||||
const phaseDirName = fs.readdirSync(phasesDir).find((name) => {
|
||||
const m = name.match(/^(\d+)/);
|
||||
return m && parseInt(m[1], 10) === parseInt(wantedPadded, 10);
|
||||
});
|
||||
if (!phaseDirName) throw new Error(`no phase directory for phase ${phase}`);
|
||||
const phaseDir = path.join(phasesDir, phaseDirName);
|
||||
fs.writeFileSync(
|
||||
path.join(phaseDir, `${phase}-VERIFICATION.md`),
|
||||
['---', 'status: passed', '---', '', '# Verification', ''].join('\n'),
|
||||
);
|
||||
return runGsdTools(args, tmpDir);
|
||||
}
|
||||
|
||||
describe('phase complete stage-3 sentinel filter (#2949)', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject('gsd-2949-');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
/** Scaffold a phase dir with one executed plan (PLAN + SUMMARY). */
|
||||
function scaffoldPhase(slug, planNum) {
|
||||
const dir = path.join(tmpDir, '.planning', 'phases', slug);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const padded = String(planNum).padStart(2, '0');
|
||||
fs.writeFileSync(path.join(dir, `${padded}-01-PLAN.md`), '# Plan');
|
||||
fs.writeFileSync(path.join(dir, `${padded}-01-SUMMARY.md`), '# Summary');
|
||||
}
|
||||
|
||||
test('zeroXSentinelDoesNotBecomeNextPhase', () => {
|
||||
// Row 1 (failing-first regression): completing the last real phase with an unchecked
|
||||
// 0.x backlog sentinel row present must NOT select the sentinel as next_phase.
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
- [ ] **Phase 0.1: Backlog sentinel item** — deferred work
|
||||
- [x] **Phase 11: First phase** (completed 2025-01-01)
|
||||
- [ ] **Phase 12: Last phase**
|
||||
|
||||
### Phase 11: First phase
|
||||
**Goal:** first
|
||||
**Plans:** 1 plans
|
||||
|
||||
### Phase 12: Last phase
|
||||
**Goal:** last
|
||||
**Plans:** 1 plans
|
||||
`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 12\n**Current Phase Name:** Last phase\n**Status:** In progress\n**Current Plan:** 12-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working on phase 12\n`,
|
||||
);
|
||||
scaffoldPhase('11-first-phase', 11);
|
||||
scaffoldPhase('12-last-phase', 12);
|
||||
|
||||
const result = runVerifiedPhaseComplete('phase complete 12', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error || result.output}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.completed_phase, '12');
|
||||
assert.strictEqual(output.is_last_phase, true, '0.x sentinel must not prevent milestone completion (is_last_phase=true)');
|
||||
assert.strictEqual(output.next_phase, null, '0.x sentinel must not be selected as next_phase');
|
||||
|
||||
// STATE.md current_phase must stay on the completed phase (12), not advance to 0.1.
|
||||
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
||||
assert.ok(!/\*\*Current Phase:\*\*\s*0\.1/i.test(state), 'STATE.md current_phase must NOT have advanced to the 0.x sentinel');
|
||||
});
|
||||
|
||||
test('realLowerOutstandingPhaseStillSelected', () => {
|
||||
// Row 2 (#2028 non-regression): a REAL lower-numbered unchecked phase must STILL be
|
||||
// selected as next_phase. The sentinel filter must not over-broaden to real phases.
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
- [ ] **Phase 9: Skipped-then-resumed phase**
|
||||
- [ ] **Phase 10: Current phase**
|
||||
|
||||
### Phase 9: Skipped-then-resumed phase
|
||||
**Goal:** nine
|
||||
**Plans:** 1 plans
|
||||
|
||||
### Phase 10: Current phase
|
||||
**Goal:** ten
|
||||
**Plans:** 1 plans
|
||||
`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 10\n**Current Phase Name:** Current phase\n**Status:** In progress\n**Current Plan:** 10-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working on phase 10\n`,
|
||||
);
|
||||
scaffoldPhase('09-skipped-then-resumed-phase', 9);
|
||||
scaffoldPhase('10-current-phase', 10);
|
||||
|
||||
const result = runVerifiedPhaseComplete('phase complete 10', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error || result.output}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// A real lower phase (9) IS selected — the #2028 out-of-order behavior is preserved.
|
||||
// next_phase may be padded ("09") or unpadded ("9"); compare numerically.
|
||||
assert.strictEqual(output.is_last_phase, false, 'a real lower outstanding phase must keep is_last_phase=false');
|
||||
const nextNum = parseInt(String(output.next_phase), 10);
|
||||
assert.strictEqual(nextNum, 9, `real lower phase 9 must be selected as next_phase (got ${output.next_phase})`);
|
||||
});
|
||||
|
||||
test('zeroXSentinelNoCurrentPhaseDesync', () => {
|
||||
// Row 3 (acceptance #3/#4): current_phase and current_phase_name must not desync when
|
||||
// a 0.x sentinel is present and the milestone completes.
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
- [ ] **Phase 0.1: Backlog**
|
||||
- [ ] **Phase 5: Only phase**
|
||||
|
||||
### Phase 5: Only phase
|
||||
**Goal:** five
|
||||
**Plans:** 1 plans
|
||||
`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 5\n**Current Phase Name:** Only phase\n**Status:** In progress\n**Current Plan:** 05-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working on phase 5\n`,
|
||||
);
|
||||
scaffoldPhase('05-only-phase', 5);
|
||||
|
||||
const result = runVerifiedPhaseComplete('phase complete 5', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error || result.output}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.is_last_phase, true, 'milestone completes despite the 0.x sentinel');
|
||||
|
||||
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
||||
// current_phase must NOT have advanced to 0.1 (no desync into the sentinel).
|
||||
assert.ok(!/\*\*Current Phase:\*\*\s*0\.1/i.test(state), 'no desync: current_phase did not advance to 0.1');
|
||||
});
|
||||
|
||||
test('checkedZeroXSentinelIrrelevant', () => {
|
||||
// Row 4 (boundary): a CHECKED 0.x sentinel is irrelevant — completing the last real phase
|
||||
// still completes the milestone.
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
- [x] **Phase 0.1: Already-done backlog item**
|
||||
- [ ] **Phase 3: Last phase**
|
||||
|
||||
### Phase 3: Last phase
|
||||
**Goal:** three
|
||||
**Plans:** 1 plans
|
||||
`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 3\n**Current Phase Name:** Last phase\n**Status:** In progress\n**Current Plan:** 03-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working on phase 3\n`,
|
||||
);
|
||||
scaffoldPhase('03-last-phase', 3);
|
||||
|
||||
const result = runVerifiedPhaseComplete('phase complete 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error || result.output}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.is_last_phase, true, 'checked sentinel is irrelevant; milestone completes');
|
||||
assert.strictEqual(output.next_phase, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user