kenaz 0.3.5 → 0.3.6
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/core/.claude/commands/Ansuz.md +1 -0
- package/core/.formation/native/claude/README.md +1 -1
- package/core/.formation/role-map.yaml +3 -3
- package/core/.formation/spec/task_operations.yaml +3 -3
- package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +4 -4
- package/core/.formation/tools/dispatch.js +5 -5
- package/core/.formation/tools/prompt.js +4 -0
- package/core/.formation/unit/do.yaml +2 -4
- package/core/.rule/general/agent-workflow.yaml +4 -4
- package/core/.rule/general/file-size-threshold.yaml +16 -4
- package/core/.rule/general/rust-verify-target.yaml +0 -1
- package/core/.rule/general/system-invariants.yaml +8 -25
- package/core/.specialist/Ansuz/SYSTEM_DOC.md +17 -42
- package/core/.specialist/Ansuz/capability.yaml +7 -14
- package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +4 -11
- package/core/.specialist/Ansuz/elixir.yaml +3 -4
- package/core/.specialist/Ansuz/identity_contract.yaml +1 -1
- package/core/.specialist/Ansuz/knowledge.yaml +7 -10
- package/core/.specialist/Ansuz/rule.yaml +2 -2
- package/core/.specialist/Ansuz/tools/verify-and-land.js +22 -0
- package/core/.specialist/Ansuz/tools/verify-and-land.test.js +59 -5
- package/core/.specialist/Ansuz/workflow.yaml +7 -11
- package/core/.specialist/Forseti/core.yaml +1 -1
- package/core/.specialist/Heimdall/SKILL.md +1 -1
- package/core/.specialist/Heimdall/core.yaml +2 -2
- package/core/.specialist/Huginn/rule.yaml +1 -1
- package/core/.specialist/tools/README.md +2 -2
- package/core/.system/collaboration/README.md +1 -1
- package/core/.system/formation/LEGACY_SKILLS.md +2 -0
- package/core/.system/formation/POST_COMPLETE.md +2 -0
- package/core/.system/formation/README.md +2 -0
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/scripts/check-file-size.js +44 -17
- package/core/k-cli/README.md +1 -94
- package/core/k-cli/test/TEST_SPEC.md +5 -22
- package/core/k-cli/test/run.sh +1 -67
- package/core/plugins/kenaz/commands/Ansuz.md +1 -0
- package/package.json +6 -6
- package/core/.formation/legion/forge.yaml +0 -299
- package/core/.formation/legion/hive.yaml +0 -238
- package/core/.formation/squad/blitz.yaml +0 -192
- package/core/.formation/squad/strategy-tribunal.yaml +0 -354
- package/core/.formation/squad/strategy.yaml +0 -349
- package/core/.formation/squad/sweep.yaml +0 -208
|
@@ -147,6 +147,8 @@ function buildPlan(config) {
|
|
|
147
147
|
plan.push({ name: 'negative-control', kind: 'neg' });
|
|
148
148
|
}
|
|
149
149
|
if (!config.noMerge) {
|
|
150
|
+
// TASK_2400: ARCH-001 file-size gate, cheap and deterministic, so before the heavy checks.
|
|
151
|
+
plan.push({ name: 'file-size', kind: 'file-size' });
|
|
150
152
|
// TASK_2176: the authoritative, serialized heavyweight check runs right
|
|
151
153
|
// before merge, on the main tree (config.repo) -- gates the merge that
|
|
152
154
|
// follows it, same as the worktree-side negative control gates it above.
|
|
@@ -759,6 +761,24 @@ function coreToolPath(config, rel) {
|
|
|
759
761
|
return path.join(__dirname, '..', '..', '..', rel);
|
|
760
762
|
}
|
|
761
763
|
|
|
764
|
+
/**
|
|
765
|
+
* TASK_2400: ARCH-001 gate. Runs the core's check-file-size.js on branch vs merge-base
|
|
766
|
+
* (--repo = the project, so a user project uses its own .rule yaml or the built-in
|
|
767
|
+
* defaults). BLOCK -> step fails, merge never runs; WARN lines go into the step summary.
|
|
768
|
+
*/
|
|
769
|
+
function stepFileSize(config, deps, ctx) {
|
|
770
|
+
const script = coreToolPath(config, path.join('dev', 'scripts', 'check-file-size.js'));
|
|
771
|
+
if (!fs.existsSync(script)) return { ok: true, summary: 'check-file-size.js not found in core, skipped', log: '' };
|
|
772
|
+
const base = git(deps, config.repo, ['merge-base', 'HEAD', ctx.branch]);
|
|
773
|
+
const res = deps.spawnSync('node', [script, '--base', base, '--head', ctx.branch, '--repo', config.repo], { encoding: 'utf8' });
|
|
774
|
+
const output = (res.stdout || '') + (res.stderr || '');
|
|
775
|
+
const found = output.split('\n').filter((l) => /ARCH-001 (BLOCK|WARN)/.test(l)).map((l) => l.trim());
|
|
776
|
+
if (res.status === 0) {
|
|
777
|
+
return { ok: true, summary: found.length ? `${found.length} warning(s): ${found.join(' | ')}`.slice(0, 600) : 'within thresholds', log: output };
|
|
778
|
+
}
|
|
779
|
+
return { ok: false, summary: `ARCH-001 blocked (exit ${res.status}): ${found.join(' | ') || output.trim()}`.slice(0, 800), log: output };
|
|
780
|
+
}
|
|
781
|
+
|
|
762
782
|
function writeLandingInfo(config, deps, ctx) {
|
|
763
783
|
if (!ctx || !ctx.postMergeHead) return; // no merge landed (--no-merge etc.) -- nothing to record
|
|
764
784
|
const shortSha = ctx.postMergeHead.slice(0, 8);
|
|
@@ -845,6 +865,7 @@ async function executeStep(stepDef, config, deps, ctx) {
|
|
|
845
865
|
case 'rust': return stepRust(config, deps, ctx, stepDef.filter);
|
|
846
866
|
case 'node': return stepNode(config, deps, ctx, stepDef.file);
|
|
847
867
|
case 'neg': return stepNegativeControl(config, deps, ctx);
|
|
868
|
+
case 'file-size': return stepFileSize(config, deps, ctx);
|
|
848
869
|
case 'verify-offline': return stepVerifyOffline(config, deps);
|
|
849
870
|
case 'merge': return stepMerge(config, deps, ctx);
|
|
850
871
|
case 'task-complete': return stepTaskComplete(config, deps, ctx);
|
|
@@ -1000,6 +1021,7 @@ module.exports = {
|
|
|
1000
1021
|
latestStartedAtAfter,
|
|
1001
1022
|
resolveRepoFromWorktree,
|
|
1002
1023
|
coreToolPath,
|
|
1024
|
+
stepFileSize,
|
|
1003
1025
|
assertRepoOwnsWorktree,
|
|
1004
1026
|
resolveRepo,
|
|
1005
1027
|
UsageError,
|
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
DEFAULT_RUST_MAX_S,
|
|
34
34
|
resolveVerifyOfflineCmd,
|
|
35
35
|
stepVerifyOffline,
|
|
36
|
+
stepFileSize,
|
|
36
37
|
} = require('./verify-and-land.js');
|
|
37
38
|
|
|
38
39
|
// Same shared list verify-and-land.js reads (TASK_2069) — tests assert
|
|
@@ -273,6 +274,7 @@ test('buildPlan: fixed step order for a full config (default warm route — no c
|
|
|
273
274
|
'rust:foo',
|
|
274
275
|
'node:t.js',
|
|
275
276
|
'negative-control',
|
|
277
|
+
'file-size',
|
|
276
278
|
'merge',
|
|
277
279
|
'task-complete',
|
|
278
280
|
'mark-landed',
|
|
@@ -362,6 +364,8 @@ test('success path: runs steps in order (rust via the warm rust-verify-worktree.
|
|
|
362
364
|
return ok('test result: ok. 5 passed; 0 failed\n');
|
|
363
365
|
}
|
|
364
366
|
if (cmd === 'node' && args[0] === '--test') return ok('ℹ pass 2\nℹ fail 0\n');
|
|
367
|
+
if (cmd === 'git' && args[2] === 'merge-base' && args[3] === 'HEAD') return ok('basesha\n');
|
|
368
|
+
if (cmd === 'node' && args[0].includes('check-file-size.js')) return ok('');
|
|
365
369
|
if (cmd === 'git' && args[2] === 'merge' && args.includes('--no-ff')) {
|
|
366
370
|
mergeCount++;
|
|
367
371
|
return ok('Merge made by the recursive strategy.\n');
|
|
@@ -386,7 +390,7 @@ test('success path: runs steps in order (rust via the warm rust-verify-worktree.
|
|
|
386
390
|
// copies its own resources into the fixed verify worktree.
|
|
387
391
|
assert.deepEqual(
|
|
388
392
|
result.steps.map(s => s.name),
|
|
389
|
-
['preflight', 'rust:foo', 'node:test/a.test.js', 'merge', 'task-complete', 'mark-landed']
|
|
393
|
+
['preflight', 'rust:foo', 'node:test/a.test.js', 'file-size', 'merge', 'task-complete', 'mark-landed']
|
|
390
394
|
);
|
|
391
395
|
assert.ok(result.steps.every(s => s.ok === true));
|
|
392
396
|
assert.equal(mergeCount, 1, 'merge must run exactly once');
|
|
@@ -416,6 +420,8 @@ test('TASK_2142 R2: task-complete bakes landed commit sha + verification summary
|
|
|
416
420
|
if (cmd === 'git' && args[2] === 'status') return ok('');
|
|
417
421
|
if (cmd === 'git' && args[2] === 'rev-list') return ok('1\n');
|
|
418
422
|
if (cmd === 'node' && args[0].includes('rust-verify-worktree.js') && args[1] === 'run') return ok('test result: ok. 3 passed; 0 failed\n');
|
|
423
|
+
if (cmd === 'git' && args[2] === 'merge-base' && args[3] === 'HEAD') return ok('basesha\n');
|
|
424
|
+
if (cmd === 'node' && args[0].includes('check-file-size.js')) return ok('');
|
|
419
425
|
if (cmd === 'git' && args[2] === 'merge' && args.includes('--no-ff')) return ok('Merge made by the recursive strategy.\n');
|
|
420
426
|
if (cmd === 'git' && args[2] === 'rev-parse' && args[3] === 'HEAD') {
|
|
421
427
|
headCount++;
|
|
@@ -772,7 +778,7 @@ test('dry-run: resolves/validates --repo via read-only git (same guard as a real
|
|
|
772
778
|
assert.equal(result.repo, config.repo, 'dry-run output includes the resolved repo path');
|
|
773
779
|
assert.deepEqual(
|
|
774
780
|
result.steps.map(s => s.name),
|
|
775
|
-
['preflight', 'rust:foo', 'node:t.js', 'negative-control', 'merge', 'task-complete', 'mark-landed', 'wait-backend', 'live']
|
|
781
|
+
['preflight', 'rust:foo', 'node:t.js', 'negative-control', 'file-size', 'merge', 'task-complete', 'mark-landed', 'wait-backend', 'live']
|
|
776
782
|
);
|
|
777
783
|
assert.ok(result.steps.every(s => s.planned === true));
|
|
778
784
|
});
|
|
@@ -923,6 +929,8 @@ test('merge verification: "Already up to date" (unchanged HEAD) fails the merge
|
|
|
923
929
|
if (cmd === 'git' && args[2] === 'status') return ok('');
|
|
924
930
|
if (cmd === 'git' && args[2] === 'rev-list') return ok('1\n');
|
|
925
931
|
if (cmd === 'node' && args[0].includes('rust-verify-worktree.js') && args[1] === 'run') return ok('test result: ok. 1 passed; 0 failed\n');
|
|
932
|
+
if (cmd === 'git' && args[2] === 'merge-base' && args[3] === 'HEAD') return ok('basesha\n');
|
|
933
|
+
if (cmd === 'node' && args[0].includes('check-file-size.js')) return ok('');
|
|
926
934
|
if (cmd === 'git' && args[2] === 'merge' && args.includes('--no-ff')) return ok('Already up to date.\n');
|
|
927
935
|
// HEAD is the SAME before and after — this is the exact bug that shipped.
|
|
928
936
|
if (cmd === 'git' && args[2] === 'rev-parse' && args[3] === 'HEAD') return ok('deadbeef\n');
|
|
@@ -934,7 +942,7 @@ test('merge verification: "Already up to date" (unchanged HEAD) fails the merge
|
|
|
934
942
|
const result = await runVerifyAndLand(config, deps);
|
|
935
943
|
assert.equal(result.ok, false);
|
|
936
944
|
assert.equal(result.failed_step, 'merge');
|
|
937
|
-
assert.deepEqual(result.steps.map(s => s.name), ['preflight', 'rust:foo', 'merge']);
|
|
945
|
+
assert.deepEqual(result.steps.map(s => s.name), ['preflight', 'rust:foo', 'file-size', 'merge']);
|
|
938
946
|
assert.match(result.steps[result.steps.length - 1].summary, /unchanged|did not land/);
|
|
939
947
|
assert.equal(taskCompleteCalled, false, 'task-api complete must never run after a merge that did not actually land');
|
|
940
948
|
});
|
|
@@ -958,7 +966,7 @@ test('resolveVerifyOfflineCmd: project.yaml without the key -> null', () => {
|
|
|
958
966
|
|
|
959
967
|
test('buildPlan: verify-offline step is included only when verifyOfflineCmd is set, placed right before merge', () => {
|
|
960
968
|
const withCmd = buildPlan(baseConfig({ verifyOfflineCmd: 'node fake-unity.js' }));
|
|
961
|
-
assert.deepEqual(withCmd.map(s => s.name), ['preflight', 'verify-offline', 'merge', 'task-complete', 'mark-landed']);
|
|
969
|
+
assert.deepEqual(withCmd.map(s => s.name), ['preflight', 'file-size', 'verify-offline', 'merge', 'task-complete', 'mark-landed']);
|
|
962
970
|
const withoutCmd = buildPlan(baseConfig({ verifyOfflineCmd: null }));
|
|
963
971
|
assert.ok(!withoutCmd.some(s => s.kind === 'verify-offline'), 'no key in project.yaml -> no step, unaffected');
|
|
964
972
|
});
|
|
@@ -1028,6 +1036,8 @@ test('runVerifyAndLand: verify_offline failure blocks landing (full pipeline, re
|
|
|
1028
1036
|
if (cmd === 'git' && args[2] === 'status') return ok('');
|
|
1029
1037
|
if (cmd === 'git' && args[2] === 'rev-list') return ok('1\n');
|
|
1030
1038
|
if (cmd === 'node fake-unity.js --break') return bad(1, '', 'compile error'); // shell:true -- cmd is the whole command string, args is []
|
|
1039
|
+
if (cmd === 'git' && args[2] === 'merge-base' && args[3] === 'HEAD') return ok('basesha\n');
|
|
1040
|
+
if (cmd === 'node' && args[0].includes('check-file-size.js')) return ok('');
|
|
1031
1041
|
if (cmd === 'git' && args[2] === 'merge') { mergeCalled = true; return ok('merged\n'); }
|
|
1032
1042
|
throw new Error('unexpected spawnSync in verify_offline-blocks-landing test: ' + JSON.stringify({ cmd, args }));
|
|
1033
1043
|
},
|
|
@@ -1035,7 +1045,7 @@ test('runVerifyAndLand: verify_offline failure blocks landing (full pipeline, re
|
|
|
1035
1045
|
const result = await runVerifyAndLand(config, deps);
|
|
1036
1046
|
assert.equal(result.ok, false);
|
|
1037
1047
|
assert.equal(result.failed_step, 'verify-offline');
|
|
1038
|
-
assert.deepEqual(result.steps.map(s => s.name), ['preflight', 'verify-offline']);
|
|
1048
|
+
assert.deepEqual(result.steps.map(s => s.name), ['preflight', 'file-size', 'verify-offline']);
|
|
1039
1049
|
assert.equal(mergeCalled, false, 'verify_offline failure must block the merge that follows it');
|
|
1040
1050
|
});
|
|
1041
1051
|
|
|
@@ -1097,3 +1107,47 @@ test('coreToolPath: a sub-project repo (only .kenaz/) resolves core tools via pr
|
|
|
1097
1107
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
1098
1108
|
}
|
|
1099
1109
|
});
|
|
1110
|
+
|
|
1111
|
+
// ── TASK_2400: ARCH-001 file-size gate (real temp repo, real check-file-size.js) ──
|
|
1112
|
+
|
|
1113
|
+
function makeSizeFixture(extraFiles) {
|
|
1114
|
+
const fsr = require('node:fs');
|
|
1115
|
+
const os = require('node:os');
|
|
1116
|
+
const repo = fsr.mkdtempSync(path.join(os.tmpdir(), 'size-gate-'));
|
|
1117
|
+
const run = (...a) => { const r = realSpawnSync('git', ['-C', repo, ...a], { encoding: 'utf8' }); assert.equal(r.status, 0, r.stderr); return r.stdout; };
|
|
1118
|
+
const body = n => Array.from({ length: n }, (_, i) => `const v${i} = ${i};`).join('\n') + '\n';
|
|
1119
|
+
run('init', '-q', '-b', 'main');
|
|
1120
|
+
run('config', 'user.email', 't@t'); run('config', 'user.name', 't');
|
|
1121
|
+
fsr.writeFileSync(path.join(repo, 'big.js'), body(2100)); // over the 2000 hard limit: grandfathered debt
|
|
1122
|
+
run('add', '-A'); run('commit', '-q', '-m', 'base');
|
|
1123
|
+
run('checkout', '-q', '-b', 'task');
|
|
1124
|
+
fsr.writeFileSync(path.join(repo, 'big.js'), body(2200)); // +100 lines
|
|
1125
|
+
for (const [name, n] of Object.entries(extraFiles || {})) fsr.writeFileSync(path.join(repo, name), body(n));
|
|
1126
|
+
run('add', '-A'); run('commit', '-q', '-m', 'grow');
|
|
1127
|
+
run('checkout', '-q', 'main');
|
|
1128
|
+
return { repo, cleanup: () => fsr.rmSync(repo, { recursive: true, force: true }) };
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
test('stepFileSize: growing an over-limit file without an extraction is refused (BLOCK)', () => {
|
|
1132
|
+
const fx = makeSizeFixture();
|
|
1133
|
+
try {
|
|
1134
|
+
const res = stepFileSize({ repo: fx.repo }, { spawnSync: realSpawnSync }, { branch: 'task' });
|
|
1135
|
+
assert.equal(res.ok, false);
|
|
1136
|
+
assert.match(res.summary, /ARCH-001 blocked/);
|
|
1137
|
+
assert.match(res.summary, /big\.js/);
|
|
1138
|
+
} finally { fx.cleanup(); }
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
test('stepFileSize: same growth paired with a new extracted module passes', () => {
|
|
1142
|
+
const fx = makeSizeFixture({ 'extracted.js': 60 });
|
|
1143
|
+
try {
|
|
1144
|
+
const res = stepFileSize({ repo: fx.repo }, { spawnSync: realSpawnSync }, { branch: 'task' });
|
|
1145
|
+
assert.equal(res.ok, true, res.summary);
|
|
1146
|
+
} finally { fx.cleanup(); }
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
test('buildPlan: file-size runs before merge, and not at all with --no-merge', () => {
|
|
1150
|
+
const names = buildPlan(baseConfig({})).map(s => s.name);
|
|
1151
|
+
assert.deepEqual(names, ['preflight', 'file-size', 'merge', 'task-complete', 'mark-landed']);
|
|
1152
|
+
assert.ok(!buildPlan(baseConfig({ noMerge: true })).some(s => s.name === 'file-size'));
|
|
1153
|
+
});
|
|
@@ -94,7 +94,7 @@ phase_0_initialization:
|
|
|
94
94
|
1. 檢查當前工作目錄(cwd)是否有 .kenaz/project.yaml
|
|
95
95
|
2. 如果有 → 子專案模式:設定 PROJECT_PATH = cwd, CORE = project.yaml 的 core_path
|
|
96
96
|
3. 如果沒有 → Core 模式:PROJECT_PATH 為空,不需要 --project-path
|
|
97
|
-
4. 後續所有 task-api.js /
|
|
97
|
+
4. 後續所有 task-api.js / dispatch.js / post-complete.js 呼叫都根據此結果決定是否加 --project-path
|
|
98
98
|
check: |
|
|
99
99
|
# 子專案偵測(在 cwd 執行)
|
|
100
100
|
if [ -f .kenaz/project.yaml ]; then
|
|
@@ -753,19 +753,15 @@ phase_8_dispatch:
|
|
|
753
753
|
|
|
754
754
|
# ─── Dispatch 執行 ───
|
|
755
755
|
execution:
|
|
756
|
-
tool: ".formation/tools/
|
|
757
|
-
method: "Bash tool → node
|
|
758
|
-
max_parallel: 5
|
|
756
|
+
tool: ".formation/tools/dispatch.js"
|
|
757
|
+
method: "Bash tool → node dispatch.js --dispatch-mode=subagent → 取回 promptText + agentParams → Agent tool 開 subagent"
|
|
759
758
|
patterns:
|
|
760
|
-
single: "一個任務 →
|
|
761
|
-
parallel: "多個無依賴 → 多個 Bash(background) 同時啟動(上限 5,超過排隊)"
|
|
759
|
+
single: "一個任務 → node dispatch.js --task=TASK_XXX --dispatch-mode=subagent [--project-path ...] → Agent tool"
|
|
762
760
|
sequential: "有依賴 → 前一個完成再 dispatch 下一個"
|
|
763
761
|
cli:
|
|
764
|
-
unit: "node {CORE}/.formation/tools/
|
|
765
|
-
solo: "node {CORE}/.formation/tools/
|
|
766
|
-
|
|
767
|
-
legion: "node {CORE}/.formation/tools/headless.js --formation=legion --skill=forge --task=TASK_XXX [--project-path {PROJECT_PATH}]"
|
|
768
|
-
anti_pattern: "❌ 禁止用 Task tool 直接開 subagent — 繞過 Formation 會失去 prompt 組裝、principles 注入和 log"
|
|
762
|
+
unit: "node {CORE}/.formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent [--project-path {PROJECT_PATH}]"
|
|
763
|
+
solo: "node {CORE}/.formation/tools/dispatch.js --formation=solo --skill=strategy --task=TASK_XXX --dispatch-mode=subagent [--project-path {PROJECT_PATH}]"
|
|
764
|
+
anti_pattern: "❌ 不手寫 subagent prompt — 用 dispatch.js 組裝(prompt 組裝、principles 注入)"
|
|
769
765
|
|
|
770
766
|
# ─── Scope 安全 ───
|
|
771
767
|
scope:
|
|
@@ -162,7 +162,7 @@ degradation:
|
|
|
162
162
|
# ═══════════════════════════════════════
|
|
163
163
|
invocation:
|
|
164
164
|
via_formation:
|
|
165
|
-
command: "node .formation/tools/
|
|
165
|
+
command: "node .formation/tools/dispatch.js --skill=do --task={FORSETI_TASK_ID} --project-path={path} --dispatch-mode=subagent"
|
|
166
166
|
context_required:
|
|
167
167
|
- "task_id: 被驗證的 Dev Task ID(必填)"
|
|
168
168
|
- "project_path: /absolute/path/to/project(必填)"
|
|
@@ -49,7 +49,7 @@ post_complete_verdict: RECORDED | delta: {N.NN|n/a} | reason: {text}
|
|
|
49
49
|
| 項目 | 說明 |
|
|
50
50
|
|------|------|
|
|
51
51
|
| 路徑 | `.kenaz/health_snapshots/{taskId}_before.json` / `_after.json` |
|
|
52
|
-
| 寫入者 |
|
|
52
|
+
| 寫入者 | 首次運行時由 post-complete.js 寫入 |
|
|
53
53
|
| 格式 | `{ "score": 82, "grade": "B", "timestamp": "..." }` |
|
|
54
54
|
| 無快照時 | 保存當前分數為基線 |
|
|
55
55
|
|
|
@@ -211,7 +211,7 @@ workflow:
|
|
|
211
211
|
- "✅ Always write post_complete_verdict: RECORDED to the task YAML when a post-task score exists"
|
|
212
212
|
pre_task_snapshot:
|
|
213
213
|
description: "Health baseline captured before Formation dispatch"
|
|
214
|
-
written_by: "
|
|
214
|
+
written_by: "post-complete.js (first run when no baseline exists)"
|
|
215
215
|
path: ".kenaz/health_snapshots/{task_id}_before.json"
|
|
216
216
|
schema: "{ score: number, grade: string, timestamp: ISO-8601 }"
|
|
217
217
|
|
|
@@ -233,7 +233,7 @@ degradation:
|
|
|
233
233
|
# ═══════════════════════════════════════
|
|
234
234
|
invocation:
|
|
235
235
|
via_formation:
|
|
236
|
-
command: "node .formation/tools/
|
|
236
|
+
command: "node .formation/tools/dispatch.js --skill=do --task=HEIMDALL_SCAN --project-path={path} --dispatch-mode=subagent"
|
|
237
237
|
context_required:
|
|
238
238
|
- "trigger: attention | commit | scheduled | manual"
|
|
239
239
|
- "project_path: /absolute/path/to/project"
|
|
@@ -50,7 +50,7 @@ deny:
|
|
|
50
50
|
- "寫程式碼或修改 src/ 下任何檔案"
|
|
51
51
|
- "修改其他 specialist 的檔案"
|
|
52
52
|
- "建立 .collaboration/tasks/(那是 Ansuz 的事)"
|
|
53
|
-
- "使用 .formation/tools/
|
|
53
|
+
- "使用 .formation/tools/dispatch.js --dispatch-mode=subagent 調度任何任務"
|
|
54
54
|
- "呼叫 hippocampus_episode_start/append/close"
|
|
55
55
|
- "修改 .rule/ 下任何規則(安全邊界 — Huginn 不能改自己的規則)"
|
|
56
56
|
- "建立 proposal(Prefrontal 提案系統已移除 — 只寫 huginn_advisory.jsonl)"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
| 模式 | 說明 | 用途 |
|
|
21
21
|
|------|------|------|
|
|
22
|
-
| **assembled**(預設) | 將所有 YAML 內容內嵌到 prompt |
|
|
22
|
+
| **assembled**(預設) | 將所有 YAML 內容內嵌到 prompt | dispatch.js、API、Dashboard |
|
|
23
23
|
| **reference** | 回傳檔案讀取指令(同 slash command) | Claude Code 內互動 |
|
|
24
24
|
|
|
25
25
|
### CLI 用法
|
|
@@ -79,7 +79,7 @@ const active = session.getActiveSessions();
|
|
|
79
79
|
|
|
80
80
|
### 整合範例
|
|
81
81
|
|
|
82
|
-
**Formation
|
|
82
|
+
**Formation dispatch.js**:
|
|
83
83
|
```js
|
|
84
84
|
const { buildPrompt } = require('../.specialist/tools/session');
|
|
85
85
|
const prompt = buildPrompt('Ansuz', { task: taskData, mode: 'assembled' });
|
|
@@ -156,7 +156,7 @@ node .collaboration/core/task-api.js move TASK_194 blocked --force
|
|
|
156
156
|
`project_name`);需要更精確的來源(例如具體的 in-app agent 名稱)可用 `--origin <name>` 覆寫。
|
|
157
157
|
|
|
158
158
|
**不是 auto-pipeline**:`createTask()` 一律寫入 `backlog/`(`pending` 狀態),走正常 triage 流程 ——
|
|
159
|
-
`submit` 完全不呼叫 dispatch
|
|
159
|
+
`submit` 完全不呼叫 dispatch.js,不會自動執行任務。來源內容(title/description 等)視為未信任資料,
|
|
160
160
|
與 field report 相同處理方式(寫入前不會被當作指令執行)。
|
|
161
161
|
|
|
162
162
|
**為什麼不是 HTTP server**:呼叫方本身就是一個能 spawn `node`/`k-cli` 的本地 process;開一個 HTTP server
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Formation Legacy Skills - 全角色定義
|
|
2
2
|
|
|
3
|
+
> **[RETIRED 2026-10, TASK_2401/2402/2403]** 本文描述已退役的 headless / legacy-scheduler Formation,僅留作歷史參考。現行路徑:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`(見 `.formation/README.md`)。
|
|
4
|
+
|
|
3
5
|
> **專案位置**: `.formation/legacy/`
|
|
4
6
|
> **系統類型**: Formation Legacy 陣型專用 Skills
|
|
5
7
|
> **適用場景**: Legacy Scheduler 自動調度、Formation Lead 任務分派
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Formation Post-Complete - 任務完成後自動化
|
|
2
2
|
|
|
3
|
+
> **[RETIRED CALLERS 2026-10, TASK_2401/2402/2403]** 文中的 legacy-scheduler.js / headless.js 呼叫端已移除,僅留作歷史參考;post-complete.js 本身仍存在。現行派工:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`。
|
|
4
|
+
|
|
3
5
|
> **專案位置**: `.formation/tools/post-complete.js`
|
|
4
6
|
> **系統類型**: Formation Lead 專用工具
|
|
5
7
|
> **適用場景**: 任務完成後的自動 Archive + Git 分支管理
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Formation System - 多陣型調度系統
|
|
2
2
|
|
|
3
|
+
> **[RETIRED 2026-10, TASK_2401/2402/2403]** 本文描述已退役的 headless / legacy-scheduler Formation,僅留作歷史參考。現行路徑:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`(見 `.formation/README.md`)。
|
|
4
|
+
|
|
3
5
|
> **專案位置**: `.formation/`
|
|
4
6
|
> **系統類型**: Multi-Agent 協作陣型系統
|
|
5
7
|
> **適用場景**: 根據任務規模動態選擇最佳 Agent 部署策略
|
package/core/KENAZ_CORE_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.6
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* ARCH-001 deterministic delta-aware file-size gate.
|
|
5
5
|
*
|
|
6
6
|
* Single source of truth for thresholds/scope: .rule/general/file-size-threshold.yaml
|
|
7
|
-
* (
|
|
7
|
+
* (project copy first, then core's; DEFAULT_CONFIG below when neither exists, e.g. a user
|
|
8
|
+
* project). Thresholds 2026-10-10: warn 1000 / hard 2000; existing over-limit files are
|
|
9
|
+
* grandfathered (only growth without a paired extraction blocks).
|
|
8
10
|
*
|
|
9
11
|
* Delta-aware + grandfather semantics:
|
|
10
12
|
* - BLOCK: a change newly pushes a file past hard_limit, OR grows a file
|
|
@@ -30,7 +32,6 @@ const fs = require('fs');
|
|
|
30
32
|
const path = require('path');
|
|
31
33
|
const { spawnSync } = require('child_process');
|
|
32
34
|
const yaml = (() => { try { return require('js-yaml'); } catch { return require('../../.vendor/js-yaml.cjs'); } })();
|
|
33
|
-
const micromatch = require('micromatch');
|
|
34
35
|
|
|
35
36
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
36
37
|
const RULE_PATH = path.join(ROOT, '.rule', 'general', 'file-size-threshold.yaml');
|
|
@@ -40,17 +41,28 @@ const RULE_PATH = path.join(ROOT, '.rule', 'general', 'file-size-threshold.yaml'
|
|
|
40
41
|
// define this separately for warn vs hard_limit, so both cases share it.
|
|
41
42
|
const GROWTH_THRESHOLD_LOC = 50;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
// Built-in defaults, used when neither the project nor the core has the rule yaml
|
|
45
|
+
// (a user project installed from npm). Keep in sync with the yaml.
|
|
46
|
+
const DEFAULT_CONFIG = {
|
|
47
|
+
warn: 1000,
|
|
48
|
+
hardLimit: 2000,
|
|
49
|
+
include: ['**/*.rs', '**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.py', '**/*.go'],
|
|
50
|
+
exclude: [
|
|
51
|
+
'**/vendor/**', '**/target/**', '**/node_modules/**', '**/dist/**', '**/.next/**',
|
|
52
|
+
'**/*.test.*', '**/*.spec.*', '**/*_test.*', '**/tests/**', '**/__tests__/**',
|
|
53
|
+
'**/*.d.ts', '**/*.generated.*', '**/generated/**', '**/*.min.js',
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Rule yaml: the project's own first, then the core's; none found -> DEFAULT_CONFIG. */
|
|
58
|
+
function loadConfig(repoPath) {
|
|
59
|
+
const candidates = [path.join(repoPath || process.cwd(), '.rule', 'general', 'file-size-threshold.yaml'), RULE_PATH];
|
|
60
|
+
const rulePath = candidates.find((p) => fs.existsSync(p));
|
|
61
|
+
if (!rulePath) return DEFAULT_CONFIG;
|
|
62
|
+
const doc = yaml.load(fs.readFileSync(rulePath, 'utf8'));
|
|
51
63
|
const rule = (doc && doc.rules || []).find((r) => r.id === 'ARCH-001');
|
|
52
64
|
if (!rule || !rule.thresholds || !rule.scope) {
|
|
53
|
-
throw new Error(`ARCH-001: malformed rule source ${
|
|
65
|
+
throw new Error(`ARCH-001: malformed rule source ${rulePath}`);
|
|
54
66
|
}
|
|
55
67
|
return {
|
|
56
68
|
warn: rule.thresholds.warn,
|
|
@@ -60,11 +72,26 @@ function loadConfig() {
|
|
|
60
72
|
};
|
|
61
73
|
}
|
|
62
74
|
|
|
75
|
+
// The npm core ships without node_modules, so the scope globs (`**/`, `*`, `?`) are matched here.
|
|
76
|
+
function globToRegExp(glob) {
|
|
77
|
+
let re = '';
|
|
78
|
+
for (let i = 0; i < glob.length; i++) {
|
|
79
|
+
const c = glob[i];
|
|
80
|
+
if (c === '*' && glob[i + 1] === '*') {
|
|
81
|
+
i++;
|
|
82
|
+
if (glob[i + 1] === '/') { i++; re += '(?:.*/)?'; } else re += '.*';
|
|
83
|
+
} else if (c === '*') re += '[^/]*';
|
|
84
|
+
else if (c === '?') re += '[^/]';
|
|
85
|
+
else re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
86
|
+
}
|
|
87
|
+
return new RegExp('^' + re + '$');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const matchesAny = (p, globs) => globs.some((g) => globToRegExp(g).test(p));
|
|
91
|
+
|
|
63
92
|
function isInScope(relPath, config) {
|
|
64
93
|
const posixPath = relPath.split(path.sep).join('/');
|
|
65
|
-
|
|
66
|
-
if (micromatch.isMatch(posixPath, config.exclude, { dot: true })) return false;
|
|
67
|
-
return true;
|
|
94
|
+
return matchesAny(posixPath, config.include) && !matchesAny(posixPath, config.exclude);
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
function countLines(text) {
|
|
@@ -127,7 +154,7 @@ function absoluteStatus(lines, config) {
|
|
|
127
154
|
|
|
128
155
|
/** --staged: compare staged (index) content against HEAD. */
|
|
129
156
|
function runStaged(config, repoPath) {
|
|
130
|
-
const cwd = repoPath ||
|
|
157
|
+
const cwd = repoPath || process.cwd();
|
|
131
158
|
// -c core.quotePath=false: avoid octal-escaped/double-quoted non-ASCII paths
|
|
132
159
|
// in porcelain output, which would break the plain string parsing below.
|
|
133
160
|
const nameStatus = git(['-c', 'core.quotePath=false', 'diff', '--cached', '--name-status'], cwd);
|
|
@@ -217,7 +244,7 @@ function main() {
|
|
|
217
244
|
const args = parseArgs(process.argv.slice(2));
|
|
218
245
|
let config;
|
|
219
246
|
try {
|
|
220
|
-
config = loadConfig();
|
|
247
|
+
config = loadConfig(args.repo);
|
|
221
248
|
} catch (e) {
|
|
222
249
|
console.error(e.message);
|
|
223
250
|
process.exit(2);
|
|
@@ -230,7 +257,7 @@ function main() {
|
|
|
230
257
|
} else if (args.file) {
|
|
231
258
|
results = runFile(args.file, config);
|
|
232
259
|
} else if (args.base && args.head) {
|
|
233
|
-
results = runRefDiff(args.base, args.head, args.repo ||
|
|
260
|
+
results = runRefDiff(args.base, args.head, args.repo || process.cwd(), config);
|
|
234
261
|
} else {
|
|
235
262
|
console.error('Usage: check-file-size.js --staged | --file <path> | --base <ref> --head <ref> [--repo <path>]');
|
|
236
263
|
process.exit(2);
|
package/core/k-cli/README.md
CHANGED
|
@@ -33,7 +33,7 @@ cat k-cli.md
|
|
|
33
33
|
## 核心價值
|
|
34
34
|
|
|
35
35
|
- **零依賴**:不需要 Dashboard 運行,直接呼叫底層工具
|
|
36
|
-
- **spawnClaude 架構**:寫入操作透過 `spawnClaude` 直接啟動 Claude CLI
|
|
36
|
+
- **spawnClaude 架構**:寫入操作透過 `spawnClaude` 直接啟動 Claude CLI
|
|
37
37
|
- **dry-run 支援**:所有寫入命令支援 `--dry-run`,驗證但不執行
|
|
38
38
|
- **可腳本化**:所有輸出支援 `--json` 格式,方便 pipe 和自動化
|
|
39
39
|
- **明確偵測**:不猜測、不 fallback、不向上找目錄,偵測失敗就是失敗
|
|
@@ -50,8 +50,6 @@ cat k-cli.md
|
|
|
50
50
|
| 讀取 | specialist list, task list, task get, project status... | 直接呼叫 |
|
|
51
51
|
| 寫入(任務) | task create | → spawnClaude + Ansuz(分析需求 → Amelia 審查 role → 建立) |
|
|
52
52
|
| 刪除 | task delete | → 直接 rm 檔案 |
|
|
53
|
-
| 調度 | dispatch run TASK_XXX | → spawnClaude + Ansuz(讀 role → 判斷陣型 → headless.js) |
|
|
54
|
-
| 調度 | dispatch run --auto | → spawnClaude + Ansuz(掃描 backlog → 逐個 dispatch) |
|
|
55
53
|
| 執行 | specialist run | → spawnClaude + 指定 Specialist |
|
|
56
54
|
|
|
57
55
|
## 命令一覽
|
|
@@ -149,65 +147,13 @@ k-cli task create "描述"
|
|
|
149
147
|
|
|
150
148
|
| 命令 | 說明 | 執行方式 |
|
|
151
149
|
|------|------|---------|
|
|
152
|
-
| `k-cli dispatch run <taskId>` | Dispatch 指定任務 | → spawnClaude + Ansuz + headless.js |
|
|
153
|
-
| `k-cli dispatch run --auto` | 自動掃描 backlog 並 dispatch | → spawnClaude + Ansuz + headless.js |
|
|
154
150
|
| `k-cli dispatch status <taskId>` | 查看 dispatch 狀態 | 直接讀取 |
|
|
155
151
|
|
|
156
152
|
```bash
|
|
157
|
-
# 指定任務 dispatch(Ansuz 讀 role → 判斷陣型 → headless.js 執行)
|
|
158
|
-
k-cli dispatch run TASK_340
|
|
159
|
-
k-cli dispatch run TASK_340 --dry-run
|
|
160
|
-
|
|
161
|
-
# 自動掃描(Ansuz 掃 backlog → 按優先級逐個 dispatch)
|
|
162
|
-
k-cli dispatch run --auto
|
|
163
|
-
k-cli dispatch run --auto --project-path /path/to/project
|
|
164
|
-
|
|
165
153
|
# 查看狀態(自動搜尋 .kenaz/logs/ 或 .formation/logs/)
|
|
166
154
|
k-cli dispatch status TASK_340 --tail 50
|
|
167
155
|
```
|
|
168
156
|
|
|
169
|
-
#### dispatch run 流程
|
|
170
|
-
|
|
171
|
-
```
|
|
172
|
-
k-cli dispatch run TASK_XXX
|
|
173
|
-
│
|
|
174
|
-
└→ spawnClaude(Ansuz)
|
|
175
|
-
├─ Step 1: task-api get → 讀取任務 role
|
|
176
|
-
├─ Step 2: role → --skill, 判斷陣型(unit > solo > squad > legion)
|
|
177
|
-
└─ Step 3: node headless.js --skill={role} --task=TASK_XXX
|
|
178
|
-
│
|
|
179
|
-
headless.js 自動處理:
|
|
180
|
-
├─ auto-claim(backlog → active)
|
|
181
|
-
├─ 依賴預檢
|
|
182
|
-
├─ spawnClaude(agent) 執行任務
|
|
183
|
-
├─ ensureTaskComplete
|
|
184
|
-
└─ postComplete(archive + git branch + merge)
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
#### Formation Role → Skill 對應
|
|
188
|
-
|
|
189
|
-
| role | --skill | 說明 |
|
|
190
|
-
|------|---------|------|
|
|
191
|
-
| frontend | frontend | React/TypeScript UI、client-side |
|
|
192
|
-
| backend | backend | API、DB、server-side |
|
|
193
|
-
| designer | designer | 設計規範產出 |
|
|
194
|
-
| qa | qa | 測試、Bug 驗證 |
|
|
195
|
-
| sre | sre | 基礎設施、部署 |
|
|
196
|
-
| developer | developer | 通才兜底 |
|
|
197
|
-
|
|
198
|
-
Skill 定義檔:`.formation/legacy/{role}.yaml`
|
|
199
|
-
|
|
200
|
-
#### 陣型判斷
|
|
201
|
-
|
|
202
|
-
原則:能 unit 不 solo,能 solo 不 squad,能 squad 不 legion。
|
|
203
|
-
|
|
204
|
-
| 陣型 | 條件 | headless.js 參數 |
|
|
205
|
-
|------|------|-----------------|
|
|
206
|
-
| unit(預設) | 目標明確、單一 scope | `--skill={role} --task=TASK_XXX` |
|
|
207
|
-
| solo | 需多角度分析 | `--formation=solo --skill=strategy --task=TASK_XXX` |
|
|
208
|
-
| squad | 多個獨立子任務可並行 | `--formation=squad --skill=blitz --task=TASK_XXX` |
|
|
209
|
-
| legion | 有順序依賴或即時協調 | `--formation=legion --skill=forge --task=TASK_XXX` |
|
|
210
|
-
|
|
211
157
|
### Project 命令
|
|
212
158
|
|
|
213
159
|
| 命令 | 說明 | 執行方式 |
|
|
@@ -274,7 +220,6 @@ k-cli project install k-cli --dry-run
|
|
|
274
220
|
| `--dry-run` | 驗證但不執行(適用所有寫入 / 調度命令) |
|
|
275
221
|
| `--json` | 輸出 JSON 格式 |
|
|
276
222
|
| `--project-path <path>` | 指定子專案路徑(使用 `.kenaz/project.yaml` 中的 `project_path` 值) |
|
|
277
|
-
| `--auto` | 自動掃描模式(僅 `dispatch run`) |
|
|
278
223
|
| `--force` | 跳過品質檢查(僅 `task create`) |
|
|
279
224
|
| `--no-git` | 初始化時跳過 Git(僅 `project initialize`) |
|
|
280
225
|
| `--core-path <path>` | 覆蓋自動偵測的 core 路徑(僅 `project initialize`) |
|
|
@@ -306,7 +251,6 @@ k-cli/
|
|
|
306
251
|
│ │ ├── update.ts # task update(直接 task-api.js)
|
|
307
252
|
│ │ └── delete.ts # task delete(直接 task-api.js)
|
|
308
253
|
│ ├── dispatch/ # Formation 調度功能
|
|
309
|
-
│ │ ├── run.ts # dispatch run(spawnClaude + Ansuz + headless.js)
|
|
310
254
|
│ │ └── status.ts # dispatch status(直接讀 log)
|
|
311
255
|
│ └── project/ # 專案管理功能
|
|
312
256
|
│ ├── status.ts # project status(含 project.yaml 詳情)
|
|
@@ -333,10 +277,6 @@ k-cli
|
|
|
333
277
|
│ ├─ task/create → Ansuz 分析需求
|
|
334
278
|
│ │ → Agent tool subagent(Amelia 審查 + role)
|
|
335
279
|
│ │ → task-api.js create(最終建立)
|
|
336
|
-
│ ├─ dispatch/run → Ansuz 讀 role + 判斷陣型
|
|
337
|
-
│ │ TASK_XXX → headless.js --skill={role} --task=TASK_XXX
|
|
338
|
-
│ └─ dispatch/run → Ansuz 掃 backlog + 逐個 dispatch
|
|
339
|
-
│ --auto → headless.js × N
|
|
340
280
|
│
|
|
341
281
|
└─ spawnClaude(Specialist 直接執行)
|
|
342
282
|
└─ specialist/run → session.js(組裝 prompt)+ spawnClaude
|
|
@@ -354,38 +294,6 @@ spawnClaude(opts)
|
|
|
354
294
|
└─ spawn(claude, args) → stdin pipe prompt, stream-json 輸出
|
|
355
295
|
```
|
|
356
296
|
|
|
357
|
-
### Git 流程(自動觸發鏈)
|
|
358
|
-
|
|
359
|
-
```
|
|
360
|
-
k-cli dispatch run TASK_XXX
|
|
361
|
-
└→ Ansuz → headless.js
|
|
362
|
-
├─ auto-claim(backlog → active)
|
|
363
|
-
├─ 依賴預檢
|
|
364
|
-
├─ 保存當前分支 → .formation/.dispatch-branch
|
|
365
|
-
├─ spawnClaude(agent) 執行任務
|
|
366
|
-
└─ agent exit 0 → 自動觸發:
|
|
367
|
-
├─ ensureTaskComplete → task-api complete
|
|
368
|
-
└─ postComplete()
|
|
369
|
-
├─ archive(done/ 超過 10 個 → archived/)
|
|
370
|
-
└─ gitBranchAndMerge()
|
|
371
|
-
├─ stash 未提交變更
|
|
372
|
-
├─ checkout -b task/task_xxx
|
|
373
|
-
├─ unstash + git add + commit
|
|
374
|
-
├─ checkout {userBranch}
|
|
375
|
-
├─ merge task/task_xxx --no-ff
|
|
376
|
-
└─ 提示: merge to main when ready
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
| 階段 | 誰處理 | 自動/手動 |
|
|
380
|
-
|------|--------|----------|
|
|
381
|
-
| claim | headless.js | 自動 |
|
|
382
|
-
| agent 執行 | headless.js → spawnClaude | 自動 |
|
|
383
|
-
| task complete | headless.js ensureTaskComplete() | 自動 |
|
|
384
|
-
| archive | post-complete.js | 自動 |
|
|
385
|
-
| task branch + commit | post-complete.js gitBranchAndMerge() | 自動 |
|
|
386
|
-
| merge 到 user branch | post-complete.js | 自動 |
|
|
387
|
-
| merge 到 main | **用戶** | **手動** |
|
|
388
|
-
|
|
389
297
|
## Dry-Run 行為
|
|
390
298
|
|
|
391
299
|
`--dry-run` 適用於所有有副作用的命令:
|
|
@@ -393,7 +301,6 @@ k-cli dispatch run TASK_XXX
|
|
|
393
301
|
| 命令 | dry-run 行為 |
|
|
394
302
|
|------|-------------|
|
|
395
303
|
| `specialist run` | 組裝 prompt → 顯示 agent + prompt 長度 → **不 spawn Claude** |
|
|
396
|
-
| `dispatch run` | 組裝 Ansuz prompt → 顯示模式 + 資訊 → **不 spawn Ansuz** |
|
|
397
304
|
| `task create` | 組裝 Ansuz prompt → 顯示將建立的任務資訊 → **不啟動 Ansuz** |
|
|
398
305
|
| `task update` | 組裝 task-api args → 顯示將更新的內容 → **不呼叫 task-api** |
|
|
399
306
|
| `task delete` | 組裝 task-api args → 顯示將刪除的任務 → **不呼叫 task-api** |
|