@swarmdo/cli 1.37.0 → 1.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/dist/src/apply/apply.d.ts.map +1 -1
- package/dist/src/apply/apply.js +6 -1
- package/dist/src/apply/apply.js.map +1 -1
- package/dist/src/changelog/changelog.d.ts +21 -0
- package/dist/src/changelog/changelog.d.ts.map +1 -1
- package/dist/src/changelog/changelog.js +43 -0
- package/dist/src/changelog/changelog.js.map +1 -1
- package/dist/src/commands/changelog.d.ts.map +1 -1
- package/dist/src/commands/changelog.js +15 -4
- package/dist/src/commands/changelog.js.map +1 -1
- package/dist/src/commands/config.d.ts.map +1 -1
- package/dist/src/commands/config.js +37 -1
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/coupling.d.ts +17 -0
- package/dist/src/commands/coupling.d.ts.map +1 -0
- package/dist/src/commands/coupling.js +85 -0
- package/dist/src/commands/coupling.js.map +1 -0
- package/dist/src/commands/hidden-coupling.d.ts +19 -0
- package/dist/src/commands/hidden-coupling.d.ts.map +1 -0
- package/dist/src/commands/hidden-coupling.js +92 -0
- package/dist/src/commands/hidden-coupling.js.map +1 -0
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +55 -0
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/hotspots.d.ts.map +1 -1
- package/dist/src/commands/hotspots.js +2 -1
- package/dist/src/commands/hotspots.js.map +1 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +12 -3
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/ownership.d.ts +18 -0
- package/dist/src/commands/ownership.d.ts.map +1 -0
- package/dist/src/commands/ownership.js +88 -0
- package/dist/src/commands/ownership.js.map +1 -0
- package/dist/src/commands/pack.js +4 -4
- package/dist/src/commands/pack.js.map +1 -1
- package/dist/src/commands/redact.d.ts.map +1 -1
- package/dist/src/commands/redact.js +11 -0
- package/dist/src/commands/redact.js.map +1 -1
- package/dist/src/commands/release.d.ts.map +1 -1
- package/dist/src/commands/release.js +27 -5
- package/dist/src/commands/release.js.map +1 -1
- package/dist/src/commands/task.d.ts.map +1 -1
- package/dist/src/commands/task.js +57 -2
- package/dist/src/commands/task.js.map +1 -1
- package/dist/src/config-lint/agents-lint.d.ts +30 -0
- package/dist/src/config-lint/agents-lint.d.ts.map +1 -0
- package/dist/src/config-lint/agents-lint.js +87 -0
- package/dist/src/config-lint/agents-lint.js.map +1 -0
- package/dist/src/config-lint/commands-lint.d.ts +31 -0
- package/dist/src/config-lint/commands-lint.d.ts.map +1 -0
- package/dist/src/config-lint/commands-lint.js +127 -0
- package/dist/src/config-lint/commands-lint.js.map +1 -0
- package/dist/src/config-lint/lint.d.ts +8 -0
- package/dist/src/config-lint/lint.d.ts.map +1 -1
- package/dist/src/config-lint/lint.js +7 -0
- package/dist/src/config-lint/lint.js.map +1 -1
- package/dist/src/coupling/coupling.d.ts +56 -0
- package/dist/src/coupling/coupling.d.ts.map +1 -0
- package/dist/src/coupling/coupling.js +86 -0
- package/dist/src/coupling/coupling.js.map +1 -0
- package/dist/src/coupling/hidden.d.ts +45 -0
- package/dist/src/coupling/hidden.d.ts.map +1 -0
- package/dist/src/coupling/hidden.js +66 -0
- package/dist/src/coupling/hidden.js.map +1 -0
- package/dist/src/hooks-recipe/command-guard.d.ts +46 -0
- package/dist/src/hooks-recipe/command-guard.d.ts.map +1 -0
- package/dist/src/hooks-recipe/command-guard.js +120 -0
- package/dist/src/hooks-recipe/command-guard.js.map +1 -0
- package/dist/src/hooks-recipe/recipe.d.ts.map +1 -1
- package/dist/src/hooks-recipe/recipe.js +8 -0
- package/dist/src/hooks-recipe/recipe.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +4 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-tools/coupling-tools.d.ts +14 -0
- package/dist/src/mcp-tools/coupling-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/coupling-tools.js +56 -0
- package/dist/src/mcp-tools/coupling-tools.js.map +1 -0
- package/dist/src/mcp-tools/index.d.ts +2 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +2 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/mcp-tools/ownership-tools.d.ts +14 -0
- package/dist/src/mcp-tools/ownership-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/ownership-tools.js +51 -0
- package/dist/src/mcp-tools/ownership-tools.js.map +1 -0
- package/dist/src/mcp-tools/profiles.d.ts.map +1 -1
- package/dist/src/mcp-tools/profiles.js +2 -0
- package/dist/src/mcp-tools/profiles.js.map +1 -1
- package/dist/src/mcp-tools/task-deps.d.ts +35 -0
- package/dist/src/mcp-tools/task-deps.d.ts.map +1 -1
- package/dist/src/mcp-tools/task-deps.js +87 -0
- package/dist/src/mcp-tools/task-deps.js.map +1 -1
- package/dist/src/ownership/ownership.d.ts +61 -0
- package/dist/src/ownership/ownership.d.ts.map +1 -0
- package/dist/src/ownership/ownership.js +151 -0
- package/dist/src/ownership/ownership.js.map +1 -0
- package/dist/src/pack/pack.d.ts +1 -1
- package/dist/src/pack/pack.d.ts.map +1 -1
- package/dist/src/pack/pack.js +12 -4
- package/dist/src/pack/pack.js.map +1 -1
- package/dist/src/redact/redact.d.ts.map +1 -1
- package/dist/src/redact/redact.js +47 -16
- package/dist/src/redact/redact.js.map +1 -1
- package/dist/src/redact/sarif.d.ts +28 -0
- package/dist/src/redact/sarif.d.ts.map +1 -0
- package/dist/src/redact/sarif.js +53 -0
- package/dist/src/redact/sarif.js.map +1 -0
- package/dist/src/release/release.d.ts +19 -0
- package/dist/src/release/release.d.ts.map +1 -1
- package/dist/src/release/release.js +28 -0
- package/dist/src/release/release.js.map +1 -1
- package/dist/src/usage/reflect.d.ts +9 -1
- package/dist/src/usage/reflect.d.ts.map +1 -1
- package/dist/src/usage/reflect.js +14 -4
- package/dist/src/usage/reflect.js.map +1 -1
- package/dist/src/util/since.d.ts +13 -0
- package/dist/src/util/since.d.ts.map +1 -0
- package/dist/src/util/since.js +21 -0
- package/dist/src/util/since.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `swarmdo hidden-coupling` — files that CHANGE TOGETHER but have no import edge.
|
|
3
|
+
*
|
|
4
|
+
* swarmdo hidden-coupling # co-change pairs the code doesn't explain
|
|
5
|
+
* swarmdo hidden-coupling --since 6mo --min-shared 3
|
|
6
|
+
* swarmdo hidden-coupling --format json | --csv
|
|
7
|
+
*
|
|
8
|
+
* "Logical coupling minus structural coupling": pairs with a high co-change
|
|
9
|
+
* `degree` (from `coupling`) yet NO import edge connecting them (from
|
|
10
|
+
* `codegraph`). A dependency real enough to move two files in lockstep but
|
|
11
|
+
* invisible in the code — the co-edit an agent following imports would miss, or
|
|
12
|
+
* a missing abstraction a reviewer should question. Composes the two capture
|
|
13
|
+
* paths verbatim: git-log + `computeCoupling`, and the codegraph index load.
|
|
14
|
+
* Engine (../coupling/hidden.ts) is pure + tested.
|
|
15
|
+
*/
|
|
16
|
+
import type { Command } from '../types.js';
|
|
17
|
+
export declare const hiddenCouplingCommand: Command;
|
|
18
|
+
export default hiddenCouplingCommand;
|
|
19
|
+
//# sourceMappingURL=hidden-coupling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hidden-coupling.d.ts","sourceRoot":"","sources":["../../../src/commands/hidden-coupling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,aAAa,CAAC;AA8D1E,eAAO,MAAM,qBAAqB,EAAE,OAenC,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `swarmdo hidden-coupling` — files that CHANGE TOGETHER but have no import edge.
|
|
3
|
+
*
|
|
4
|
+
* swarmdo hidden-coupling # co-change pairs the code doesn't explain
|
|
5
|
+
* swarmdo hidden-coupling --since 6mo --min-shared 3
|
|
6
|
+
* swarmdo hidden-coupling --format json | --csv
|
|
7
|
+
*
|
|
8
|
+
* "Logical coupling minus structural coupling": pairs with a high co-change
|
|
9
|
+
* `degree` (from `coupling`) yet NO import edge connecting them (from
|
|
10
|
+
* `codegraph`). A dependency real enough to move two files in lockstep but
|
|
11
|
+
* invisible in the code — the co-edit an agent following imports would miss, or
|
|
12
|
+
* a missing abstraction a reviewer should question. Composes the two capture
|
|
13
|
+
* paths verbatim: git-log + `computeCoupling`, and the codegraph index load.
|
|
14
|
+
* Engine (../coupling/hidden.ts) is pure + tested.
|
|
15
|
+
*/
|
|
16
|
+
import { execFileSync } from 'node:child_process';
|
|
17
|
+
import { output } from '../output.js';
|
|
18
|
+
import { parseGitLog } from '../hotspots/hotspots.js';
|
|
19
|
+
import { computeCoupling } from '../coupling/coupling.js';
|
|
20
|
+
import { computeHiddenCoupling, formatHiddenCoupling, hiddenCouplingToCsv } from '../coupling/hidden.js';
|
|
21
|
+
import { loadIndex, scanRepo, saveIndex } from '../codegraph/store.js';
|
|
22
|
+
import { normalizeSince } from '../util/since.js';
|
|
23
|
+
/** Read a numeric flag that the parser may deliver as a number OR a string. */
|
|
24
|
+
function numFlag(v, def) {
|
|
25
|
+
const n = typeof v === 'number' ? v : typeof v === 'string' ? parseInt(v, 10) : NaN;
|
|
26
|
+
return Number.isFinite(n) ? n : def;
|
|
27
|
+
}
|
|
28
|
+
async function run(ctx) {
|
|
29
|
+
const root = ctx.cwd || process.cwd();
|
|
30
|
+
const since = typeof ctx.flags.since === 'string' ? ctx.flags.since : '1 year ago';
|
|
31
|
+
const top = numFlag(ctx.flags.top, 30);
|
|
32
|
+
const minShared = numFlag(ctx.flags['min-shared'], 2);
|
|
33
|
+
const maxFiles = numFlag(ctx.flags['max-files'], 30);
|
|
34
|
+
const asJson = ctx.flags.format === 'json';
|
|
35
|
+
const asCsv = ctx.flags.csv === true;
|
|
36
|
+
// Capture 1: the full co-change history (same `--numstat` dump `coupling` uses;
|
|
37
|
+
// NO pathspec filter — that would strip co-changed files from each commit).
|
|
38
|
+
const args = ['log', '--no-merges', '--numstat', `--since=${normalizeSince(since)}`, '--format=format:%x01%H%x1f%aN%x1f%aI'];
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 128 * 1024 * 1024 });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
output.printError('git log failed — is this a git repository?');
|
|
45
|
+
return { success: false, exitCode: 1 };
|
|
46
|
+
}
|
|
47
|
+
// Rank ALL coupling pairs (no top yet — we cap the HIDDEN set after filtering).
|
|
48
|
+
const pairs = computeCoupling(parseGitLog(raw), { minShared, maxFiles });
|
|
49
|
+
// Capture 2: the static import graph (prefer a saved index; build one if absent).
|
|
50
|
+
let index = loadIndex(root);
|
|
51
|
+
if (!index) {
|
|
52
|
+
index = scanRepo(root);
|
|
53
|
+
try {
|
|
54
|
+
saveIndex(root, index);
|
|
55
|
+
}
|
|
56
|
+
catch { /* read-only fs — in-memory is fine */ }
|
|
57
|
+
}
|
|
58
|
+
const hidden = computeHiddenCoupling(pairs, index.imports, { top: top > 0 ? top : undefined });
|
|
59
|
+
if (asCsv) {
|
|
60
|
+
process.stdout.write(hiddenCouplingToCsv(hidden) + '\n');
|
|
61
|
+
}
|
|
62
|
+
else if (asJson) {
|
|
63
|
+
process.stdout.write(JSON.stringify({ generated: new Date().toISOString(), since, minShared, coupled: pairs.length, count: hidden.length, hidden }, null, 2) + '\n');
|
|
64
|
+
}
|
|
65
|
+
else if (hidden.length === 0) {
|
|
66
|
+
output.writeln(output.dim(`no hidden coupling found — every co-change pair has an import edge (or none met --min-shared ${minShared} / --since ${since})`));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
output.writeln(output.bold(`Hidden coupling (co-change with NO import edge, since ${since}, min-shared ${minShared})`));
|
|
70
|
+
output.writeln(formatHiddenCoupling(hidden));
|
|
71
|
+
output.writeln(output.dim(`${hidden.length} hidden of ${pairs.length} coupled pair(s) — these move together but nothing in the code links them`));
|
|
72
|
+
}
|
|
73
|
+
return { success: true, exitCode: 0 };
|
|
74
|
+
}
|
|
75
|
+
export const hiddenCouplingCommand = {
|
|
76
|
+
name: 'hidden-coupling',
|
|
77
|
+
description: 'Rank file pairs that change together in git history but have NO import edge (logical minus structural coupling) — the co-edit `affected` can\'t see',
|
|
78
|
+
options: [
|
|
79
|
+
{ name: 'since', description: 'history window, e.g. 90d or "3 months ago" (default 1 year)', type: 'string' },
|
|
80
|
+
{ name: 'min-shared', description: 'drop pairs sharing fewer than N commits (default 2)', type: 'string' },
|
|
81
|
+
{ name: 'max-files', description: 'skip commits touching more than N files (default 30; 0 = no cap)', type: 'string' },
|
|
82
|
+
{ name: 'top', description: 'keep only the top N hidden pairs (default 30; 0 = all)', type: 'string' },
|
|
83
|
+
{ name: 'csv', description: 'export the ranking as CSV (for spreadsheets)', type: 'boolean' },
|
|
84
|
+
],
|
|
85
|
+
examples: [
|
|
86
|
+
{ command: 'swarmdo hidden-coupling --since 6mo --min-shared 3', description: 'Strong co-change pairs with no code link, last 6 months' },
|
|
87
|
+
{ command: 'swarmdo hidden-coupling --format json', description: 'Machine-readable hidden-coupling report' },
|
|
88
|
+
],
|
|
89
|
+
action: run,
|
|
90
|
+
};
|
|
91
|
+
export default hiddenCouplingCommand;
|
|
92
|
+
//# sourceMappingURL=hidden-coupling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hidden-coupling.js","sourceRoot":"","sources":["../../../src/commands/hidden-coupling.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,aAAa,CAAC;AAq5K1E,eAAO,MAAM,YAAY,EAAE,OA+G1B,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -10,6 +10,7 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { bridgeSearchEntries } from '../memory/memory-bridge.js';
|
|
12
12
|
import { selectInjectionMemories, mapSearchResultsToCandidates, extractPromptFromPayload, } from '../memory-inject/select.js';
|
|
13
|
+
import { classifyCommand, extractBashCommand, denyOutput } from '../hooks-recipe/command-guard.js';
|
|
13
14
|
/**
|
|
14
15
|
* #1686 — `?? 0` only defaults null/undefined; NaN slips through and
|
|
15
16
|
* surfaces as `"NaN"` (or earlier crashed `.toFixed`) in the metrics
|
|
@@ -849,6 +850,58 @@ const memoryInjectCommand = {
|
|
|
849
850
|
}
|
|
850
851
|
},
|
|
851
852
|
};
|
|
853
|
+
/**
|
|
854
|
+
* guard-bash — the #83 PreToolUse/Bash guardrail. Reads the hook payload from
|
|
855
|
+
* stdin (or --command), classifies the bash command against a conservative
|
|
856
|
+
* destructive-command denylist, and on a match emits Claude Code's
|
|
857
|
+
* `permissionDecision: "deny"` to hard-block it. This is the only safety net
|
|
858
|
+
* that still fires under headless `claude -p --dangerously-skip-permissions`.
|
|
859
|
+
* Hook-safe: it NEVER errors and stays silent (allow) for anything not on the
|
|
860
|
+
* denylist, so it can't break a legitimate command.
|
|
861
|
+
*/
|
|
862
|
+
const guardBashCommand = {
|
|
863
|
+
name: 'guard-bash',
|
|
864
|
+
description: 'Block dangerous bash commands (PreToolUse hook): emits permissionDecision:deny on a destructive-command denylist',
|
|
865
|
+
options: [
|
|
866
|
+
{ name: 'command', short: 'c', description: 'Command to classify (else read the hook JSON from stdin)', type: 'string' },
|
|
867
|
+
{ name: 'preview', description: 'Print the human-readable verdict instead of emitting hook JSON', type: 'boolean' },
|
|
868
|
+
],
|
|
869
|
+
examples: [
|
|
870
|
+
{ command: 'swarmdo hooks guard-bash -c "rm -rf /" --preview', description: 'Check whether a command would be blocked' },
|
|
871
|
+
{ command: 'echo \'{"tool_input":{"command":"rm -rf /"}}\' | swarmdo hooks guard-bash', description: 'Run as a PreToolUse/Bash hook' },
|
|
872
|
+
],
|
|
873
|
+
action: async (ctx) => {
|
|
874
|
+
const preview = Boolean(ctx.flags.preview);
|
|
875
|
+
// A hook must NEVER break a tool call by crashing: swallow everything, default to allow.
|
|
876
|
+
try {
|
|
877
|
+
if (process.env.SWARMDO_GUARD_BASH_DISABLE === '1')
|
|
878
|
+
return { success: true };
|
|
879
|
+
let command = (ctx.flags.command || ctx.args.join(' ')).trim();
|
|
880
|
+
if (!command)
|
|
881
|
+
command = extractBashCommand(await readHookStdin());
|
|
882
|
+
if (!command)
|
|
883
|
+
return { success: true }; // nothing to classify → allow
|
|
884
|
+
const verdict = classifyCommand(command);
|
|
885
|
+
if (preview) {
|
|
886
|
+
if (verdict.block)
|
|
887
|
+
output.printError(`BLOCK: ${verdict.reason} [${verdict.rule}]`);
|
|
888
|
+
else
|
|
889
|
+
output.printSuccess('allow — no destructive pattern matched');
|
|
890
|
+
return { success: true, data: verdict };
|
|
891
|
+
}
|
|
892
|
+
// Hook mode: emit the deny JSON only on a block; silence = allow.
|
|
893
|
+
if (verdict.block) {
|
|
894
|
+
process.stdout.write(JSON.stringify(denyOutput(verdict.reason)) + '\n');
|
|
895
|
+
}
|
|
896
|
+
return { success: true };
|
|
897
|
+
}
|
|
898
|
+
catch (err) {
|
|
899
|
+
if (preview)
|
|
900
|
+
output.printError(`guard-bash failed: ${String(err)}`);
|
|
901
|
+
return { success: true }; // never fail the hook (fail-open: a broken guard must not block work)
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
};
|
|
852
905
|
// Explain subcommand
|
|
853
906
|
const explainCommand = {
|
|
854
907
|
name: 'explain',
|
|
@@ -4672,6 +4725,7 @@ export const hooksCommand = {
|
|
|
4672
4725
|
sessionRestoreCommand,
|
|
4673
4726
|
routeCommand,
|
|
4674
4727
|
memoryInjectCommand,
|
|
4728
|
+
guardBashCommand,
|
|
4675
4729
|
explainCommand,
|
|
4676
4730
|
pretrainCommand,
|
|
4677
4731
|
buildAgentsCommand,
|
|
@@ -4730,6 +4784,7 @@ export const hooksCommand = {
|
|
|
4730
4784
|
`${output.highlight('session-restore')} - Restore a previous session`,
|
|
4731
4785
|
`${output.highlight('route')} - Route tasks to optimal agents`,
|
|
4732
4786
|
`${output.highlight('memory-inject')} - Inject relevant memories into the prompt (UserPromptSubmit)`,
|
|
4787
|
+
`${output.highlight('guard-bash')} - Block dangerous bash commands (PreToolUse deny)`,
|
|
4733
4788
|
`${output.highlight('explain')} - Explain routing decisions`,
|
|
4734
4789
|
`${output.highlight('pretrain')} - Bootstrap intelligence from repository`,
|
|
4735
4790
|
`${output.highlight('build-agents')} - Generate optimized agent configs`,
|