deepflow 0.1.91 → 0.1.93
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/bin/ratchet.js +67 -2
- package/bin/ratchet.test.js +308 -4
- package/bin/wave-runner.js +259 -0
- package/bin/wave-runner.test.js +556 -0
- package/hooks/df-subagent-registry.js +34 -0
- package/hooks/df-subagent-registry.test.js +357 -0
- package/package.json +1 -1
- package/src/commands/df/execute.md +83 -8
package/bin/ratchet.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* deepflow ratchet
|
|
4
4
|
* Mechanical health-check gate with auto-revert on failure.
|
|
5
5
|
*
|
|
6
|
-
* Usage: node bin/ratchet.js
|
|
6
|
+
* Usage: node bin/ratchet.js [--task T{N}] [--worktree PATH] [--snapshot PATH]
|
|
7
7
|
*
|
|
8
8
|
* Outputs exactly one JSON line to stdout:
|
|
9
9
|
* {"result":"PASS"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Exit codes: 0=PASS, 1=FAIL, 2=SALVAGEABLE
|
|
14
14
|
* On FAIL: executes `git revert HEAD --no-edit` before exiting.
|
|
15
|
+
* On PASS + --task T{N}: updates PLAN.md [ ] → [x] and appends commit hash.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
'use strict';
|
|
@@ -277,18 +278,79 @@ const STAGE_ORDER = ['build', 'test', 'typecheck', 'lint'];
|
|
|
277
278
|
// Stages where failure is SALVAGEABLE (not FAIL)
|
|
278
279
|
const SALVAGEABLE_STAGES = new Set(['lint']);
|
|
279
280
|
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// CLI argument parser
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
function parseArgs(argv) {
|
|
286
|
+
const args = { task: null, worktree: null, snapshot: null };
|
|
287
|
+
for (let i = 0; i < argv.length; i++) {
|
|
288
|
+
if (argv[i] === '--task' && argv[i + 1]) {
|
|
289
|
+
args.task = argv[++i];
|
|
290
|
+
} else if (argv[i] === '--worktree' && argv[i + 1]) {
|
|
291
|
+
args.worktree = argv[++i];
|
|
292
|
+
} else if (argv[i] === '--snapshot' && argv[i + 1]) {
|
|
293
|
+
args.snapshot = argv[++i];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return args;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// PLAN.md updater
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
function updatePlanMd(repoRoot, taskId, cwd) {
|
|
304
|
+
const planPath = path.join(repoRoot, 'PLAN.md');
|
|
305
|
+
if (!fs.existsSync(planPath)) return;
|
|
306
|
+
|
|
307
|
+
let hash = '';
|
|
308
|
+
try {
|
|
309
|
+
hash = execFileSync('git', ['rev-parse', '--short', 'HEAD'], {
|
|
310
|
+
encoding: 'utf8',
|
|
311
|
+
cwd,
|
|
312
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
313
|
+
}).trim();
|
|
314
|
+
} catch (_) {
|
|
315
|
+
// best-effort
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const text = fs.readFileSync(planPath, 'utf8');
|
|
319
|
+
// Match lines like: - [ ] **T54** ...
|
|
320
|
+
const re = new RegExp(`(^.*- \\[ \\].*\\*\\*${taskId}\\*\\*.*)`, 'm');
|
|
321
|
+
const updated = text.replace(re, (line) => {
|
|
322
|
+
let result = line.replace('- [ ]', '- [x]');
|
|
323
|
+
if (hash) result += ` (${hash})`;
|
|
324
|
+
return result;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (updated !== text) {
|
|
328
|
+
fs.writeFileSync(planPath, updated, 'utf8');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
280
332
|
// ---------------------------------------------------------------------------
|
|
281
333
|
// Main
|
|
282
334
|
// ---------------------------------------------------------------------------
|
|
283
335
|
|
|
284
336
|
function main() {
|
|
285
|
-
const
|
|
337
|
+
const cliArgs = parseArgs(process.argv.slice(2));
|
|
338
|
+
const cwd = cliArgs.worktree || process.cwd();
|
|
286
339
|
const repoRoot = mainRepoRoot(cwd);
|
|
287
340
|
|
|
288
341
|
const cfg = loadConfig(repoRoot);
|
|
289
342
|
const projectType = detectProjectType(repoRoot);
|
|
290
343
|
const snapshotFiles = loadSnapshotFiles(repoRoot);
|
|
291
344
|
const cmds = buildCommands(repoRoot, projectType, snapshotFiles, cfg);
|
|
345
|
+
// --snapshot flag overrides the snapshot-derived test command
|
|
346
|
+
if (cliArgs.snapshot && fs.existsSync(cliArgs.snapshot)) {
|
|
347
|
+
const snapFiles = fs.readFileSync(cliArgs.snapshot, 'utf8')
|
|
348
|
+
.split('\n').map(l => l.trim()).filter(l => l.length > 0)
|
|
349
|
+
.map(rel => path.isAbsolute(rel) ? rel : path.join(repoRoot, rel));
|
|
350
|
+
if (snapFiles.length > 0 && projectType === 'node' && !cfg.test_command) {
|
|
351
|
+
cmds.test = ['node', '--test', ...snapFiles];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
292
354
|
|
|
293
355
|
for (const stage of STAGE_ORDER) {
|
|
294
356
|
const cmd = cmds[stage];
|
|
@@ -321,6 +383,9 @@ function main() {
|
|
|
321
383
|
}
|
|
322
384
|
|
|
323
385
|
process.stdout.write(JSON.stringify({ result: 'PASS' }) + '\n');
|
|
386
|
+
if (cliArgs.task) {
|
|
387
|
+
updatePlanMd(repoRoot, cliArgs.task, cwd);
|
|
388
|
+
}
|
|
324
389
|
process.exit(0);
|
|
325
390
|
}
|
|
326
391
|
|
package/bin/ratchet.test.js
CHANGED
|
@@ -46,12 +46,10 @@ function rmrf(dir) {
|
|
|
46
46
|
// ---------------------------------------------------------------------------
|
|
47
47
|
|
|
48
48
|
const extractedFns = (() => {
|
|
49
|
-
// Replace the main() call at the end with exports
|
|
50
49
|
const modifiedSrc = RATCHET_SRC
|
|
51
|
-
.replace(/^main\(\);?\s*$/m, '')
|
|
52
|
-
.replace(/^#!.*$/m, '');
|
|
50
|
+
.replace(/^main\(\);?\s*$/m, '')
|
|
51
|
+
.replace(/^#!.*$/m, '');
|
|
53
52
|
|
|
54
|
-
// Wrap in a function that returns the internal functions
|
|
55
53
|
const wrapped = `
|
|
56
54
|
${modifiedSrc}
|
|
57
55
|
return {
|
|
@@ -61,6 +59,8 @@ const extractedFns = (() => {
|
|
|
61
59
|
parseCommand,
|
|
62
60
|
hasNpmScript,
|
|
63
61
|
buildCommands,
|
|
62
|
+
parseArgs,
|
|
63
|
+
updatePlanMd,
|
|
64
64
|
};
|
|
65
65
|
`;
|
|
66
66
|
|
|
@@ -75,6 +75,8 @@ const {
|
|
|
75
75
|
parseCommand,
|
|
76
76
|
hasNpmScript,
|
|
77
77
|
buildCommands,
|
|
78
|
+
parseArgs,
|
|
79
|
+
updatePlanMd,
|
|
78
80
|
} = extractedFns;
|
|
79
81
|
|
|
80
82
|
// ---------------------------------------------------------------------------
|
|
@@ -867,3 +869,305 @@ describe('Structural invariants — source assertions', () => {
|
|
|
867
869
|
assert.equal(lastNonEmpty.trim(), 'main();');
|
|
868
870
|
});
|
|
869
871
|
});
|
|
872
|
+
|
|
873
|
+
// ---------------------------------------------------------------------------
|
|
874
|
+
// 14. parseArgs — CLI argument parser
|
|
875
|
+
// ---------------------------------------------------------------------------
|
|
876
|
+
|
|
877
|
+
describe('parseArgs — parses --task, --worktree, --snapshot flags', () => {
|
|
878
|
+
test('returns all nulls when no flags provided', () => {
|
|
879
|
+
const args = parseArgs([]);
|
|
880
|
+
assert.deepEqual(args, { task: null, worktree: null, snapshot: null });
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test('parses --task flag', () => {
|
|
884
|
+
const args = parseArgs(['--task', 'T54']);
|
|
885
|
+
assert.equal(args.task, 'T54');
|
|
886
|
+
assert.equal(args.worktree, null);
|
|
887
|
+
assert.equal(args.snapshot, null);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test('parses --worktree flag', () => {
|
|
891
|
+
const args = parseArgs(['--worktree', '/some/path']);
|
|
892
|
+
assert.equal(args.worktree, '/some/path');
|
|
893
|
+
assert.equal(args.task, null);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
test('parses --snapshot flag', () => {
|
|
897
|
+
const args = parseArgs(['--snapshot', '/snap/auto-snapshot.txt']);
|
|
898
|
+
assert.equal(args.snapshot, '/snap/auto-snapshot.txt');
|
|
899
|
+
assert.equal(args.task, null);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
test('parses all three flags together', () => {
|
|
903
|
+
const args = parseArgs(['--task', 'T12', '--worktree', '/w', '--snapshot', '/s']);
|
|
904
|
+
assert.equal(args.task, 'T12');
|
|
905
|
+
assert.equal(args.worktree, '/w');
|
|
906
|
+
assert.equal(args.snapshot, '/s');
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
test('parses flags in any order', () => {
|
|
910
|
+
const args = parseArgs(['--snapshot', '/s', '--task', 'T99', '--worktree', '/w']);
|
|
911
|
+
assert.equal(args.task, 'T99');
|
|
912
|
+
assert.equal(args.worktree, '/w');
|
|
913
|
+
assert.equal(args.snapshot, '/s');
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test('ignores unknown flags', () => {
|
|
917
|
+
const args = parseArgs(['--unknown', 'val', '--task', 'T1']);
|
|
918
|
+
assert.equal(args.task, 'T1');
|
|
919
|
+
assert.equal(args.worktree, null);
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
test('ignores flag without a value (at end of argv)', () => {
|
|
923
|
+
const args = parseArgs(['--task']);
|
|
924
|
+
assert.equal(args.task, null);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
test('ignores flag when next arg is missing (end of array)', () => {
|
|
928
|
+
const args = parseArgs(['--worktree']);
|
|
929
|
+
assert.equal(args.worktree, null);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
test('handles task ID with various formats', () => {
|
|
933
|
+
assert.equal(parseArgs(['--task', 'T1']).task, 'T1');
|
|
934
|
+
assert.equal(parseArgs(['--task', 'T100']).task, 'T100');
|
|
935
|
+
assert.equal(parseArgs(['--task', 'some-string']).task, 'some-string');
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
test('last value wins when flag is repeated', () => {
|
|
939
|
+
const args = parseArgs(['--task', 'T1', '--task', 'T2']);
|
|
940
|
+
assert.equal(args.task, 'T2');
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// ---------------------------------------------------------------------------
|
|
945
|
+
// 15. updatePlanMd — PLAN.md checkbox updater
|
|
946
|
+
// ---------------------------------------------------------------------------
|
|
947
|
+
|
|
948
|
+
describe('updatePlanMd — updates PLAN.md task checkboxes', () => {
|
|
949
|
+
let tmpDir;
|
|
950
|
+
|
|
951
|
+
beforeEach(() => {
|
|
952
|
+
tmpDir = makeTmpDir();
|
|
953
|
+
// Initialize git repo so rev-parse works
|
|
954
|
+
execFileSync('git', ['init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
955
|
+
execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: tmpDir, stdio: 'ignore' });
|
|
956
|
+
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: tmpDir, stdio: 'ignore' });
|
|
957
|
+
fs.writeFileSync(path.join(tmpDir, 'dummy.txt'), 'x');
|
|
958
|
+
execFileSync('git', ['add', '.'], { cwd: tmpDir, stdio: 'ignore' });
|
|
959
|
+
execFileSync('git', ['commit', '-m', 'init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
afterEach(() => { rmrf(tmpDir); });
|
|
963
|
+
|
|
964
|
+
test('checks off matching task and appends commit hash', () => {
|
|
965
|
+
fs.writeFileSync(
|
|
966
|
+
path.join(tmpDir, 'PLAN.md'),
|
|
967
|
+
'# Plan\n- [ ] **T54** Write ratchet tests\n- [ ] **T55** Other task\n'
|
|
968
|
+
);
|
|
969
|
+
updatePlanMd(tmpDir, 'T54', tmpDir);
|
|
970
|
+
|
|
971
|
+
const result = fs.readFileSync(path.join(tmpDir, 'PLAN.md'), 'utf8');
|
|
972
|
+
assert.ok(result.includes('- [x] **T54** Write ratchet tests'));
|
|
973
|
+
assert.ok(!result.includes('- [ ] **T54**'));
|
|
974
|
+
// Should have a commit hash appended
|
|
975
|
+
assert.match(result, /- \[x\] \*\*T54\*\* Write ratchet tests \([a-f0-9]+\)/);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
test('does not modify other tasks', () => {
|
|
979
|
+
fs.writeFileSync(
|
|
980
|
+
path.join(tmpDir, 'PLAN.md'),
|
|
981
|
+
'- [ ] **T54** Task A\n- [ ] **T55** Task B\n'
|
|
982
|
+
);
|
|
983
|
+
updatePlanMd(tmpDir, 'T54', tmpDir);
|
|
984
|
+
|
|
985
|
+
const result = fs.readFileSync(path.join(tmpDir, 'PLAN.md'), 'utf8');
|
|
986
|
+
assert.ok(result.includes('- [ ] **T55** Task B'));
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
test('does nothing when PLAN.md does not exist', () => {
|
|
990
|
+
// No PLAN.md file — should not throw
|
|
991
|
+
assert.doesNotThrow(() => updatePlanMd(tmpDir, 'T54', tmpDir));
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
test('does nothing when task ID not found in PLAN.md', () => {
|
|
995
|
+
const content = '- [ ] **T99** Some other task\n';
|
|
996
|
+
fs.writeFileSync(path.join(tmpDir, 'PLAN.md'), content);
|
|
997
|
+
updatePlanMd(tmpDir, 'T54', tmpDir);
|
|
998
|
+
|
|
999
|
+
const result = fs.readFileSync(path.join(tmpDir, 'PLAN.md'), 'utf8');
|
|
1000
|
+
assert.equal(result, content);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
test('does not re-check already checked task', () => {
|
|
1004
|
+
const content = '- [x] **T54** Already done\n';
|
|
1005
|
+
fs.writeFileSync(path.join(tmpDir, 'PLAN.md'), content);
|
|
1006
|
+
updatePlanMd(tmpDir, 'T54', tmpDir);
|
|
1007
|
+
|
|
1008
|
+
const result = fs.readFileSync(path.join(tmpDir, 'PLAN.md'), 'utf8');
|
|
1009
|
+
// The regex specifically matches "- [ ]" (unchecked), so already-checked should be unchanged
|
|
1010
|
+
assert.equal(result, content);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
test('handles PLAN.md with extra content around the task line', () => {
|
|
1014
|
+
fs.writeFileSync(
|
|
1015
|
+
path.join(tmpDir, 'PLAN.md'),
|
|
1016
|
+
'# Implementation Plan\n\n## Phase 1\n- [ ] **T10** First task — details here\n\n## Phase 2\n- [ ] **T20** Second task\n'
|
|
1017
|
+
);
|
|
1018
|
+
updatePlanMd(tmpDir, 'T10', tmpDir);
|
|
1019
|
+
|
|
1020
|
+
const result = fs.readFileSync(path.join(tmpDir, 'PLAN.md'), 'utf8');
|
|
1021
|
+
assert.ok(result.includes('- [x] **T10** First task'));
|
|
1022
|
+
assert.ok(result.includes('- [ ] **T20** Second task'));
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
test('appends hash even with complex task description', () => {
|
|
1026
|
+
fs.writeFileSync(
|
|
1027
|
+
path.join(tmpDir, 'PLAN.md'),
|
|
1028
|
+
'- [ ] **T7** Implement `parseArgs()` + `updatePlanMd()` in bin/ratchet.js\n'
|
|
1029
|
+
);
|
|
1030
|
+
updatePlanMd(tmpDir, 'T7', tmpDir);
|
|
1031
|
+
|
|
1032
|
+
const result = fs.readFileSync(path.join(tmpDir, 'PLAN.md'), 'utf8');
|
|
1033
|
+
assert.match(result, /- \[x\] \*\*T7\*\*.*\([a-f0-9]+\)/);
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// ---------------------------------------------------------------------------
|
|
1038
|
+
// 16. --snapshot CLI flag integration with main logic — source assertions
|
|
1039
|
+
// ---------------------------------------------------------------------------
|
|
1040
|
+
|
|
1041
|
+
describe('--snapshot flag — overrides snapshot-derived test command', () => {
|
|
1042
|
+
test('source reads --snapshot file and overrides cmds.test for node projects', () => {
|
|
1043
|
+
assert.ok(
|
|
1044
|
+
RATCHET_SRC.includes('cliArgs.snapshot'),
|
|
1045
|
+
'main() should reference cliArgs.snapshot'
|
|
1046
|
+
);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
test('source checks snapshot file existence before reading', () => {
|
|
1050
|
+
assert.ok(
|
|
1051
|
+
RATCHET_SRC.includes("fs.existsSync(cliArgs.snapshot)"),
|
|
1052
|
+
'Should check if snapshot file exists'
|
|
1053
|
+
);
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
test('source only overrides test when no cfg.test_command and node project', () => {
|
|
1057
|
+
// The condition: projectType === 'node' && !cfg.test_command
|
|
1058
|
+
assert.ok(
|
|
1059
|
+
RATCHET_SRC.includes("projectType === 'node'") && RATCHET_SRC.includes('!cfg.test_command'),
|
|
1060
|
+
'Snapshot override should be gated on node project type and no config test_command'
|
|
1061
|
+
);
|
|
1062
|
+
});
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
// ---------------------------------------------------------------------------
|
|
1066
|
+
// 17. --task flag integration — updatePlanMd called only on PASS
|
|
1067
|
+
// ---------------------------------------------------------------------------
|
|
1068
|
+
|
|
1069
|
+
describe('--task flag — updatePlanMd called only on PASS', () => {
|
|
1070
|
+
test('updatePlanMd is called after PASS output and before exit(0)', () => {
|
|
1071
|
+
const passIdx = RATCHET_SRC.indexOf("result: 'PASS'");
|
|
1072
|
+
const updateIdx = RATCHET_SRC.indexOf('updatePlanMd(repoRoot, cliArgs.task, cwd)');
|
|
1073
|
+
const exitIdx = RATCHET_SRC.indexOf('process.exit(0)');
|
|
1074
|
+
assert.ok(passIdx !== -1, 'PASS output should exist');
|
|
1075
|
+
assert.ok(updateIdx !== -1, 'updatePlanMd call should exist');
|
|
1076
|
+
assert.ok(exitIdx !== -1, 'process.exit(0) should exist');
|
|
1077
|
+
assert.ok(passIdx < updateIdx, 'updatePlanMd should be after PASS output');
|
|
1078
|
+
assert.ok(updateIdx < exitIdx, 'updatePlanMd should be before exit(0)');
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
test('updatePlanMd is guarded by cliArgs.task check', () => {
|
|
1082
|
+
assert.ok(
|
|
1083
|
+
RATCHET_SRC.includes('if (cliArgs.task)'),
|
|
1084
|
+
'updatePlanMd call should be guarded by cliArgs.task truthiness check'
|
|
1085
|
+
);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test('FAIL path does not call updatePlanMd', () => {
|
|
1089
|
+
// Between the FAIL output and exit(1), there should be no updatePlanMd
|
|
1090
|
+
const failIdx = RATCHET_SRC.indexOf("result: 'FAIL'");
|
|
1091
|
+
const exit1Idx = RATCHET_SRC.indexOf('process.exit(1)');
|
|
1092
|
+
const block = RATCHET_SRC.slice(failIdx, exit1Idx);
|
|
1093
|
+
assert.ok(!block.includes('updatePlanMd'), 'FAIL path should not call updatePlanMd');
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
test('SALVAGEABLE path does not call updatePlanMd', () => {
|
|
1097
|
+
const salvIdx = RATCHET_SRC.indexOf("result: 'SALVAGEABLE'");
|
|
1098
|
+
const exit2Idx = RATCHET_SRC.indexOf('process.exit(2)');
|
|
1099
|
+
const block = RATCHET_SRC.slice(salvIdx, exit2Idx);
|
|
1100
|
+
assert.ok(!block.includes('updatePlanMd'), 'SALVAGEABLE path should not call updatePlanMd');
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
// ---------------------------------------------------------------------------
|
|
1105
|
+
// 18. Subprocess integration — --task flag with real execution
|
|
1106
|
+
// ---------------------------------------------------------------------------
|
|
1107
|
+
|
|
1108
|
+
describe('Subprocess integration — --task flag updates PLAN.md on PASS', () => {
|
|
1109
|
+
let tmpDir;
|
|
1110
|
+
|
|
1111
|
+
beforeEach(() => {
|
|
1112
|
+
tmpDir = makeTmpDir();
|
|
1113
|
+
execFileSync('git', ['init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1114
|
+
execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1115
|
+
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
afterEach(() => { rmrf(tmpDir); });
|
|
1119
|
+
|
|
1120
|
+
test('--task flag is passed through to ratchet process (PASS still returned)', () => {
|
|
1121
|
+
// Note: mainRepoRoot resolution for worktrees means PLAN.md update
|
|
1122
|
+
// behavior is tested via direct updatePlanMd unit tests above.
|
|
1123
|
+
// Here we verify the --task flag doesn't break normal PASS behavior.
|
|
1124
|
+
fs.writeFileSync(path.join(tmpDir, 'dummy.txt'), 'hello');
|
|
1125
|
+
execFileSync('git', ['add', '.'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1126
|
+
execFileSync('git', ['commit', '-m', 'init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1127
|
+
|
|
1128
|
+
const result = execFileSync(process.execPath, [RATCHET_PATH, '--task', 'T42'], {
|
|
1129
|
+
cwd: tmpDir,
|
|
1130
|
+
encoding: 'utf8',
|
|
1131
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
const parsed = JSON.parse(result.trim());
|
|
1135
|
+
assert.equal(parsed.result, 'PASS');
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
test('--task does not update PLAN.md when no PLAN.md exists', () => {
|
|
1139
|
+
fs.writeFileSync(path.join(tmpDir, 'dummy.txt'), 'hello');
|
|
1140
|
+
execFileSync('git', ['add', '.'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1141
|
+
execFileSync('git', ['commit', '-m', 'init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1142
|
+
|
|
1143
|
+
const result = execFileSync(process.execPath, [RATCHET_PATH, '--task', 'T42'], {
|
|
1144
|
+
cwd: tmpDir,
|
|
1145
|
+
encoding: 'utf8',
|
|
1146
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
const parsed = JSON.parse(result.trim());
|
|
1150
|
+
assert.equal(parsed.result, 'PASS');
|
|
1151
|
+
// No PLAN.md should exist (it wasn't created)
|
|
1152
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, 'PLAN.md')));
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
test('without --task flag, PLAN.md is not modified', () => {
|
|
1156
|
+
fs.writeFileSync(
|
|
1157
|
+
path.join(tmpDir, 'PLAN.md'),
|
|
1158
|
+
'- [ ] **T42** Do the thing\n'
|
|
1159
|
+
);
|
|
1160
|
+
fs.writeFileSync(path.join(tmpDir, 'dummy.txt'), 'hello');
|
|
1161
|
+
execFileSync('git', ['add', '.'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1162
|
+
execFileSync('git', ['commit', '-m', 'init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
1163
|
+
|
|
1164
|
+
execFileSync(process.execPath, [RATCHET_PATH], {
|
|
1165
|
+
cwd: tmpDir,
|
|
1166
|
+
encoding: 'utf8',
|
|
1167
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
const plan = fs.readFileSync(path.join(tmpDir, 'PLAN.md'), 'utf8');
|
|
1171
|
+
assert.ok(plan.includes('- [ ] **T42**'), 'PLAN.md should remain unchecked without --task');
|
|
1172
|
+
});
|
|
1173
|
+
});
|