@blockspool/cli 0.4.1 → 0.4.3
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/dist/bin/blockspool.d.ts +16 -0
- package/dist/bin/blockspool.d.ts.map +1 -0
- package/dist/bin/blockspool.js +45 -0
- package/dist/bin/blockspool.js.map +1 -0
- package/dist/commands/solo-auto.d.ts +6 -0
- package/dist/commands/solo-auto.d.ts.map +1 -0
- package/dist/commands/solo-auto.js +418 -0
- package/dist/commands/solo-auto.js.map +1 -0
- package/dist/commands/solo-exec.d.ts +6 -0
- package/dist/commands/solo-exec.d.ts.map +1 -0
- package/dist/commands/solo-exec.js +656 -0
- package/dist/commands/solo-exec.js.map +1 -0
- package/dist/commands/solo-inspect.d.ts +6 -0
- package/dist/commands/solo-inspect.d.ts.map +1 -0
- package/dist/commands/solo-inspect.js +690 -0
- package/dist/commands/solo-inspect.js.map +1 -0
- package/dist/commands/solo-lifecycle.d.ts +6 -0
- package/dist/commands/solo-lifecycle.d.ts.map +1 -0
- package/dist/commands/solo-lifecycle.js +188 -0
- package/dist/commands/solo-lifecycle.js.map +1 -0
- package/dist/commands/solo-nudge.d.ts +6 -0
- package/dist/commands/solo-nudge.d.ts.map +1 -0
- package/dist/commands/solo-nudge.js +49 -0
- package/dist/commands/solo-nudge.js.map +1 -0
- package/dist/commands/solo-qa.d.ts +6 -0
- package/dist/commands/solo-qa.d.ts.map +1 -0
- package/dist/commands/solo-qa.js +254 -0
- package/dist/commands/solo-qa.js.map +1 -0
- package/dist/commands/solo.d.ts +11 -0
- package/dist/commands/solo.d.ts.map +1 -0
- package/dist/commands/solo.js +43 -0
- package/dist/commands/solo.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/artifacts.d.ts +136 -0
- package/dist/lib/artifacts.d.ts.map +1 -0
- package/dist/lib/artifacts.js +146 -0
- package/dist/lib/artifacts.js.map +1 -0
- package/dist/lib/doctor.d.ts +45 -0
- package/dist/lib/doctor.d.ts.map +1 -0
- package/dist/lib/doctor.js +383 -0
- package/dist/lib/doctor.js.map +1 -0
- package/dist/lib/exec.d.ts +24 -0
- package/dist/lib/exec.d.ts.map +1 -0
- package/dist/lib/exec.js +295 -0
- package/dist/lib/exec.js.map +1 -0
- package/dist/lib/formulas.d.ts +78 -0
- package/dist/lib/formulas.d.ts.map +1 -0
- package/dist/lib/formulas.js +295 -0
- package/dist/lib/formulas.js.map +1 -0
- package/dist/lib/git.d.ts +9 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +60 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/guidelines.d.ts +43 -0
- package/dist/lib/guidelines.d.ts.map +1 -0
- package/dist/lib/guidelines.js +195 -0
- package/dist/lib/guidelines.js.map +1 -0
- package/dist/lib/logger.d.ts +17 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +42 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/retention.d.ts +62 -0
- package/dist/lib/retention.d.ts.map +1 -0
- package/dist/lib/retention.js +285 -0
- package/dist/lib/retention.js.map +1 -0
- package/dist/lib/run-history.d.ts +52 -0
- package/dist/lib/run-history.d.ts.map +1 -0
- package/dist/lib/run-history.js +116 -0
- package/dist/lib/run-history.js.map +1 -0
- package/dist/lib/run-state.d.ts +58 -0
- package/dist/lib/run-state.d.ts.map +1 -0
- package/dist/lib/run-state.js +119 -0
- package/dist/lib/run-state.js.map +1 -0
- package/dist/lib/scope.d.ts +95 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +291 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/selection.d.ts +35 -0
- package/dist/lib/selection.d.ts.map +1 -0
- package/dist/lib/selection.js +110 -0
- package/dist/lib/selection.js.map +1 -0
- package/dist/lib/solo-auto.d.ts +87 -0
- package/dist/lib/solo-auto.d.ts.map +1 -0
- package/dist/lib/solo-auto.js +1230 -0
- package/dist/lib/solo-auto.js.map +1 -0
- package/dist/lib/solo-ci.d.ts +84 -0
- package/dist/lib/solo-ci.d.ts.map +1 -0
- package/dist/lib/solo-ci.js +300 -0
- package/dist/lib/solo-ci.js.map +1 -0
- package/dist/lib/solo-config.d.ts +155 -0
- package/dist/lib/solo-config.d.ts.map +1 -0
- package/dist/lib/solo-config.js +236 -0
- package/dist/lib/solo-config.js.map +1 -0
- package/dist/lib/solo-git.d.ts +44 -0
- package/dist/lib/solo-git.d.ts.map +1 -0
- package/dist/lib/solo-git.js +174 -0
- package/dist/lib/solo-git.js.map +1 -0
- package/dist/lib/solo-hints.d.ts +32 -0
- package/dist/lib/solo-hints.d.ts.map +1 -0
- package/dist/lib/solo-hints.js +98 -0
- package/dist/lib/solo-hints.js.map +1 -0
- package/dist/lib/solo-remote.d.ts +14 -0
- package/dist/lib/solo-remote.d.ts.map +1 -0
- package/dist/lib/solo-remote.js +48 -0
- package/dist/lib/solo-remote.js.map +1 -0
- package/dist/lib/solo-stdin.d.ts +13 -0
- package/dist/lib/solo-stdin.d.ts.map +1 -0
- package/dist/lib/solo-stdin.js +33 -0
- package/dist/lib/solo-stdin.js.map +1 -0
- package/dist/lib/solo-ticket.d.ts +213 -0
- package/dist/lib/solo-ticket.d.ts.map +1 -0
- package/dist/lib/solo-ticket.js +850 -0
- package/dist/lib/solo-ticket.js.map +1 -0
- package/dist/lib/solo-utils.d.ts +133 -0
- package/dist/lib/solo-utils.d.ts.map +1 -0
- package/dist/lib/solo-utils.js +300 -0
- package/dist/lib/solo-utils.js.map +1 -0
- package/dist/lib/spindle.d.ts +144 -0
- package/dist/lib/spindle.d.ts.map +1 -0
- package/dist/lib/spindle.js +388 -0
- package/dist/lib/spindle.js.map +1 -0
- package/dist/tui/app.d.ts +17 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +139 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +7 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/poller.d.ts +42 -0
- package/dist/tui/poller.d.ts.map +1 -0
- package/dist/tui/poller.js +62 -0
- package/dist/tui/poller.js.map +1 -0
- package/dist/tui/screens/overview.d.ts +9 -0
- package/dist/tui/screens/overview.d.ts.map +1 -0
- package/dist/tui/screens/overview.js +189 -0
- package/dist/tui/screens/overview.js.map +1 -0
- package/dist/tui/state.d.ts +93 -0
- package/dist/tui/state.d.ts.map +1 -0
- package/dist/tui/state.js +169 -0
- package/dist/tui/state.js.map +1 -0
- package/dist/tui/types.d.ts +18 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +5 -0
- package/dist/tui/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent run state for cross-session cycle tracking.
|
|
3
|
+
*
|
|
4
|
+
* Stored in `.blockspool/run-state.json`. Tracks how many scout cycles
|
|
5
|
+
* have run so periodic tasks (like docs-audit) can trigger automatically.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
const RUN_STATE_FILE = 'run-state.json';
|
|
10
|
+
function statePath(repoRoot) {
|
|
11
|
+
return path.join(repoRoot, '.blockspool', RUN_STATE_FILE);
|
|
12
|
+
}
|
|
13
|
+
const DEFAULT_STATE = {
|
|
14
|
+
totalCycles: 0,
|
|
15
|
+
lastDocsAuditCycle: 0,
|
|
16
|
+
lastRunAt: 0,
|
|
17
|
+
deferredProposals: [],
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Read the current run state from disk.
|
|
21
|
+
*/
|
|
22
|
+
export function readRunState(repoRoot) {
|
|
23
|
+
const fp = statePath(repoRoot);
|
|
24
|
+
if (!fs.existsSync(fp))
|
|
25
|
+
return { ...DEFAULT_STATE };
|
|
26
|
+
try {
|
|
27
|
+
const raw = fs.readFileSync(fp, 'utf-8');
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
return {
|
|
30
|
+
totalCycles: parsed.totalCycles ?? 0,
|
|
31
|
+
lastDocsAuditCycle: parsed.lastDocsAuditCycle ?? 0,
|
|
32
|
+
lastRunAt: parsed.lastRunAt ?? 0,
|
|
33
|
+
deferredProposals: Array.isArray(parsed.deferredProposals) ? parsed.deferredProposals : [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { ...DEFAULT_STATE };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Write the run state to disk.
|
|
42
|
+
*/
|
|
43
|
+
export function writeRunState(repoRoot, state) {
|
|
44
|
+
const fp = statePath(repoRoot);
|
|
45
|
+
const dir = path.dirname(fp);
|
|
46
|
+
if (!fs.existsSync(dir)) {
|
|
47
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
fs.writeFileSync(fp, JSON.stringify(state, null, 2) + '\n');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Increment the cycle counter and return the new state.
|
|
53
|
+
*/
|
|
54
|
+
export function recordCycle(repoRoot) {
|
|
55
|
+
const state = readRunState(repoRoot);
|
|
56
|
+
state.totalCycles += 1;
|
|
57
|
+
state.lastRunAt = Date.now();
|
|
58
|
+
writeRunState(repoRoot, state);
|
|
59
|
+
return state;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if a docs-audit cycle is due.
|
|
63
|
+
* Returns true every N cycles since the last docs-audit.
|
|
64
|
+
*/
|
|
65
|
+
export function isDocsAuditDue(repoRoot, interval = 3) {
|
|
66
|
+
const state = readRunState(repoRoot);
|
|
67
|
+
return (state.totalCycles - state.lastDocsAuditCycle) >= interval;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Record that a docs-audit was run.
|
|
71
|
+
*/
|
|
72
|
+
export function recordDocsAudit(repoRoot) {
|
|
73
|
+
const state = readRunState(repoRoot);
|
|
74
|
+
state.lastDocsAuditCycle = state.totalCycles;
|
|
75
|
+
writeRunState(repoRoot, state);
|
|
76
|
+
}
|
|
77
|
+
/** Max age for deferred proposals (7 days) */
|
|
78
|
+
const MAX_DEFERRED_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
79
|
+
/**
|
|
80
|
+
* Defer a proposal for later when the scope matches.
|
|
81
|
+
*/
|
|
82
|
+
export function deferProposal(repoRoot, proposal) {
|
|
83
|
+
const state = readRunState(repoRoot);
|
|
84
|
+
// Avoid duplicates by title
|
|
85
|
+
if (state.deferredProposals.some(d => d.title === proposal.title))
|
|
86
|
+
return;
|
|
87
|
+
state.deferredProposals.push(proposal);
|
|
88
|
+
writeRunState(repoRoot, state);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Retrieve and remove deferred proposals that now match the given scope.
|
|
92
|
+
* Also prunes proposals older than 7 days.
|
|
93
|
+
*/
|
|
94
|
+
export function popDeferredForScope(repoRoot, scope) {
|
|
95
|
+
const state = readRunState(repoRoot);
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const normalizedScope = scope.replace(/\*\*$/, '').replace(/\*$/, '').replace(/\/$/, '');
|
|
98
|
+
const matched = [];
|
|
99
|
+
const remaining = [];
|
|
100
|
+
for (const dp of state.deferredProposals) {
|
|
101
|
+
// Prune stale
|
|
102
|
+
if (now - dp.deferredAt > MAX_DEFERRED_AGE_MS)
|
|
103
|
+
continue;
|
|
104
|
+
const files = dp.files.length > 0 ? dp.files : dp.allowed_paths;
|
|
105
|
+
const inScope = !normalizedScope || files.length === 0 || files.every(f => f.startsWith(normalizedScope) || f.startsWith(normalizedScope + '/'));
|
|
106
|
+
if (inScope) {
|
|
107
|
+
matched.push(dp);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
remaining.push(dp);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (matched.length > 0 || remaining.length !== state.deferredProposals.length) {
|
|
114
|
+
state.deferredProposals = remaining;
|
|
115
|
+
writeRunState(repoRoot, state);
|
|
116
|
+
}
|
|
117
|
+
return matched;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=run-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-state.js","sourceRoot":"","sources":["../../src/lib/run-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope enforcement utilities
|
|
3
|
+
*
|
|
4
|
+
* Validates that file changes stay within ticket's allowed paths
|
|
5
|
+
* and don't touch forbidden paths.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Scope violation result
|
|
9
|
+
*/
|
|
10
|
+
export interface ScopeViolation {
|
|
11
|
+
file: string;
|
|
12
|
+
violation: 'not_in_allowed' | 'in_forbidden';
|
|
13
|
+
pattern?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Normalize a file path by:
|
|
17
|
+
* - Removing leading ./
|
|
18
|
+
* - Collapsing multiple slashes (//)
|
|
19
|
+
* - Removing trailing slashes (except for root)
|
|
20
|
+
*
|
|
21
|
+
* @param filePath - The file path to normalize
|
|
22
|
+
* @returns Normalized path
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizePath(filePath: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Detect if a path appears to be hallucinated by Claude.
|
|
27
|
+
* Common hallucination patterns include repeated path segments like 'foo/foo/'.
|
|
28
|
+
*
|
|
29
|
+
* @param filePath - The file path to check
|
|
30
|
+
* @returns Object with isHallucinated flag and optional reason
|
|
31
|
+
*/
|
|
32
|
+
export declare function detectHallucinatedPath(filePath: string): {
|
|
33
|
+
isHallucinated: boolean;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Check if changed files violate ticket scope constraints
|
|
38
|
+
*
|
|
39
|
+
* @param changedFiles - List of file paths that were modified
|
|
40
|
+
* @param allowedPaths - Glob patterns for allowed paths (empty = all allowed)
|
|
41
|
+
* @param forbiddenPaths - Glob patterns for forbidden paths
|
|
42
|
+
* @returns List of violations, empty if all files are within scope
|
|
43
|
+
*/
|
|
44
|
+
export declare function checkScopeViolations(changedFiles: string[], allowedPaths: string[], forbiddenPaths: string[]): ScopeViolation[];
|
|
45
|
+
/**
|
|
46
|
+
* Simple glob-style pattern matching
|
|
47
|
+
* Supports: * (any chars), ** (any path segments), ? (single char)
|
|
48
|
+
*
|
|
49
|
+
* @param filePath - The file path to check
|
|
50
|
+
* @param pattern - Glob pattern to match against
|
|
51
|
+
* @returns true if the file matches the pattern
|
|
52
|
+
*/
|
|
53
|
+
export declare function matchesPattern(filePath: string, pattern: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Result of analyzing scope violations for auto-expansion
|
|
56
|
+
*/
|
|
57
|
+
export interface ScopeExpansionResult {
|
|
58
|
+
/** Whether the paths can be expanded to fix violations */
|
|
59
|
+
canExpand: boolean;
|
|
60
|
+
/** New allowed paths after expansion */
|
|
61
|
+
expandedPaths: string[];
|
|
62
|
+
/** Files that were added to allowed paths */
|
|
63
|
+
addedPaths: string[];
|
|
64
|
+
/** Reason if expansion is not possible */
|
|
65
|
+
reason?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Analyze scope violations and determine if paths can be auto-expanded.
|
|
69
|
+
*
|
|
70
|
+
* This enables "organic" handling of blocked tickets by automatically
|
|
71
|
+
* expanding allowed_paths for reasonable related files:
|
|
72
|
+
* - Same directory (sibling files)
|
|
73
|
+
* - Test files for source files
|
|
74
|
+
* - Type definition files (.d.ts)
|
|
75
|
+
* - Index files
|
|
76
|
+
*
|
|
77
|
+
* Will NOT expand for:
|
|
78
|
+
* - Hallucinated paths
|
|
79
|
+
* - Forbidden path violations
|
|
80
|
+
* - Files in completely unrelated directories
|
|
81
|
+
*
|
|
82
|
+
* @param violations - List of scope violations from checkScopeViolations
|
|
83
|
+
* @param currentAllowedPaths - Current allowed_paths for the ticket
|
|
84
|
+
* @param maxExpansions - Maximum number of paths to add (default: 10)
|
|
85
|
+
* @returns Expansion result with new paths or reason for rejection
|
|
86
|
+
*/
|
|
87
|
+
export declare function analyzeViolationsForExpansion(violations: ScopeViolation[], currentAllowedPaths: string[], maxExpansions?: number): ScopeExpansionResult;
|
|
88
|
+
/**
|
|
89
|
+
* Parse git status --porcelain output to get changed file paths
|
|
90
|
+
*
|
|
91
|
+
* @param statusOutput - Output from `git status --porcelain`
|
|
92
|
+
* @returns List of file paths that were changed
|
|
93
|
+
*/
|
|
94
|
+
export declare function parseChangedFiles(statusOutput: string): string[];
|
|
95
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/lib/scope.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,gBAAgB,GAAG,cAAc,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACxD,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAwBA;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EAAE,EACtB,YAAY,EAAE,MAAM,EAAE,EACtB,cAAc,EAAE,MAAM,EAAE,GACvB,cAAc,EAAE,CAkDlB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAyBzE;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,SAAS,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,cAAc,EAAE,EAC5B,mBAAmB,EAAE,MAAM,EAAE,EAC7B,aAAa,GAAE,MAAW,GACzB,oBAAoB,CAyItB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAehE"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope enforcement utilities
|
|
3
|
+
*
|
|
4
|
+
* Validates that file changes stay within ticket's allowed paths
|
|
5
|
+
* and don't touch forbidden paths.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Normalize a file path by:
|
|
9
|
+
* - Removing leading ./
|
|
10
|
+
* - Collapsing multiple slashes (//)
|
|
11
|
+
* - Removing trailing slashes (except for root)
|
|
12
|
+
*
|
|
13
|
+
* @param filePath - The file path to normalize
|
|
14
|
+
* @returns Normalized path
|
|
15
|
+
*/
|
|
16
|
+
export function normalizePath(filePath) {
|
|
17
|
+
return filePath
|
|
18
|
+
.replace(/\\/g, '/') // Normalize backslashes
|
|
19
|
+
.replace(/^\.\//, '') // Remove leading ./
|
|
20
|
+
.replace(/\/+/g, '/') // Collapse multiple slashes
|
|
21
|
+
.replace(/\/$/, ''); // Remove trailing slash
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Detect if a path appears to be hallucinated by Claude.
|
|
25
|
+
* Common hallucination patterns include repeated path segments like 'foo/foo/'.
|
|
26
|
+
*
|
|
27
|
+
* @param filePath - The file path to check
|
|
28
|
+
* @returns Object with isHallucinated flag and optional reason
|
|
29
|
+
*/
|
|
30
|
+
export function detectHallucinatedPath(filePath) {
|
|
31
|
+
const normalized = normalizePath(filePath);
|
|
32
|
+
const segments = normalized.split('/').filter(s => s.length > 0);
|
|
33
|
+
// Check for repeated consecutive segments (e.g., 'cloud/cloud/', 'src/src/')
|
|
34
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
35
|
+
if (segments[i] === segments[i + 1] && segments[i].length > 0) {
|
|
36
|
+
return {
|
|
37
|
+
isHallucinated: true,
|
|
38
|
+
reason: `Repeated path segment: '${segments[i]}/${segments[i]}'`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Check for obviously malformed patterns
|
|
43
|
+
if (/\/\//.test(filePath)) {
|
|
44
|
+
// Double slashes in original (before normalization)
|
|
45
|
+
return {
|
|
46
|
+
isHallucinated: true,
|
|
47
|
+
reason: 'Contains double slashes',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { isHallucinated: false };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if changed files violate ticket scope constraints
|
|
54
|
+
*
|
|
55
|
+
* @param changedFiles - List of file paths that were modified
|
|
56
|
+
* @param allowedPaths - Glob patterns for allowed paths (empty = all allowed)
|
|
57
|
+
* @param forbiddenPaths - Glob patterns for forbidden paths
|
|
58
|
+
* @returns List of violations, empty if all files are within scope
|
|
59
|
+
*/
|
|
60
|
+
export function checkScopeViolations(changedFiles, allowedPaths, forbiddenPaths) {
|
|
61
|
+
const violations = [];
|
|
62
|
+
for (const file of changedFiles) {
|
|
63
|
+
// Check for hallucinated paths first
|
|
64
|
+
const hallucinationCheck = detectHallucinatedPath(file);
|
|
65
|
+
if (hallucinationCheck.isHallucinated) {
|
|
66
|
+
console.warn(`[scope] Warning: Detected hallucinated path '${file}': ${hallucinationCheck.reason}`);
|
|
67
|
+
// Report hallucinated paths as not_in_allowed since they are invalid
|
|
68
|
+
violations.push({
|
|
69
|
+
file,
|
|
70
|
+
violation: 'not_in_allowed',
|
|
71
|
+
pattern: `hallucinated: ${hallucinationCheck.reason}`,
|
|
72
|
+
});
|
|
73
|
+
continue; // Skip further checks for hallucinated paths
|
|
74
|
+
}
|
|
75
|
+
// Normalize the path for consistent matching
|
|
76
|
+
const normalizedFile = normalizePath(file);
|
|
77
|
+
// Check forbidden paths first (higher priority)
|
|
78
|
+
for (const pattern of forbiddenPaths) {
|
|
79
|
+
if (matchesPattern(normalizedFile, pattern)) {
|
|
80
|
+
violations.push({ file, violation: 'in_forbidden', pattern });
|
|
81
|
+
break; // Don't check allowed if forbidden
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// If no forbidden match and allowed_paths is specified, check allowed
|
|
85
|
+
if (allowedPaths.length > 0 && !violations.some(v => v.file === file)) {
|
|
86
|
+
let isAllowed = allowedPaths.some(pattern => matchesPattern(normalizedFile, pattern));
|
|
87
|
+
// For directory entries (git shows new dirs with trailing /),
|
|
88
|
+
// check if any allowed path is under this directory
|
|
89
|
+
if (!isAllowed && file.endsWith('/')) {
|
|
90
|
+
const dirPrefix = normalizedFile + '/';
|
|
91
|
+
isAllowed = allowedPaths.some(pattern => pattern.startsWith(dirPrefix));
|
|
92
|
+
}
|
|
93
|
+
if (!isAllowed) {
|
|
94
|
+
violations.push({ file, violation: 'not_in_allowed' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return violations;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Simple glob-style pattern matching
|
|
102
|
+
* Supports: * (any chars), ** (any path segments), ? (single char)
|
|
103
|
+
*
|
|
104
|
+
* @param filePath - The file path to check
|
|
105
|
+
* @param pattern - Glob pattern to match against
|
|
106
|
+
* @returns true if the file matches the pattern
|
|
107
|
+
*/
|
|
108
|
+
export function matchesPattern(filePath, pattern) {
|
|
109
|
+
// Normalize paths
|
|
110
|
+
const normalizedFile = filePath.replace(/\\/g, '/');
|
|
111
|
+
const normalizedPattern = pattern.replace(/\\/g, '/');
|
|
112
|
+
// Convert glob to regex
|
|
113
|
+
// Step 1: Replace glob patterns with placeholders
|
|
114
|
+
let regexPattern = normalizedPattern
|
|
115
|
+
.replace(/\*\*\//g, '<<<GLOBSTAR_SLASH>>>') // **/ matches zero or more directories
|
|
116
|
+
.replace(/\*\*/g, '<<<GLOBSTAR>>>') // ** at end matches anything
|
|
117
|
+
.replace(/\*/g, '<<<STAR>>>') // * matches anything except /
|
|
118
|
+
.replace(/\?/g, '<<<QUESTION>>>'); // ? matches single char
|
|
119
|
+
// Step 2: Escape special regex chars (but not our placeholders)
|
|
120
|
+
regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
121
|
+
// Step 3: Replace placeholders with regex patterns
|
|
122
|
+
regexPattern = regexPattern
|
|
123
|
+
.replace(/<<<GLOBSTAR_SLASH>>>/g, '(?:.*/)?') // **/ = zero or more dirs
|
|
124
|
+
.replace(/<<<GLOBSTAR>>>/g, '.*') // ** = anything
|
|
125
|
+
.replace(/<<<STAR>>>/g, '[^/]*') // * = anything except /
|
|
126
|
+
.replace(/<<<QUESTION>>>/g, '[^/]'); // ? = single char except /
|
|
127
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
128
|
+
return regex.test(normalizedFile);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Analyze scope violations and determine if paths can be auto-expanded.
|
|
132
|
+
*
|
|
133
|
+
* This enables "organic" handling of blocked tickets by automatically
|
|
134
|
+
* expanding allowed_paths for reasonable related files:
|
|
135
|
+
* - Same directory (sibling files)
|
|
136
|
+
* - Test files for source files
|
|
137
|
+
* - Type definition files (.d.ts)
|
|
138
|
+
* - Index files
|
|
139
|
+
*
|
|
140
|
+
* Will NOT expand for:
|
|
141
|
+
* - Hallucinated paths
|
|
142
|
+
* - Forbidden path violations
|
|
143
|
+
* - Files in completely unrelated directories
|
|
144
|
+
*
|
|
145
|
+
* @param violations - List of scope violations from checkScopeViolations
|
|
146
|
+
* @param currentAllowedPaths - Current allowed_paths for the ticket
|
|
147
|
+
* @param maxExpansions - Maximum number of paths to add (default: 10)
|
|
148
|
+
* @returns Expansion result with new paths or reason for rejection
|
|
149
|
+
*/
|
|
150
|
+
export function analyzeViolationsForExpansion(violations, currentAllowedPaths, maxExpansions = 10) {
|
|
151
|
+
// Check for forbidden violations - these cannot be expanded
|
|
152
|
+
const forbiddenViolations = violations.filter(v => v.violation === 'in_forbidden');
|
|
153
|
+
if (forbiddenViolations.length > 0) {
|
|
154
|
+
return {
|
|
155
|
+
canExpand: false,
|
|
156
|
+
expandedPaths: currentAllowedPaths,
|
|
157
|
+
addedPaths: [],
|
|
158
|
+
reason: `Cannot expand: ${forbiddenViolations.length} file(s) match forbidden paths`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Check for hallucinated paths - these cannot be expanded
|
|
162
|
+
const hallucinatedViolations = violations.filter(v => v.pattern?.startsWith('hallucinated:'));
|
|
163
|
+
if (hallucinatedViolations.length > 0) {
|
|
164
|
+
return {
|
|
165
|
+
canExpand: false,
|
|
166
|
+
expandedPaths: currentAllowedPaths,
|
|
167
|
+
addedPaths: [],
|
|
168
|
+
reason: `Cannot expand: ${hallucinatedViolations.length} hallucinated path(s) detected`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// Get the "allowed" directories from current paths
|
|
172
|
+
const allowedDirs = new Set();
|
|
173
|
+
for (const path of currentAllowedPaths) {
|
|
174
|
+
const normalized = normalizePath(path);
|
|
175
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
176
|
+
if (lastSlash > 0) {
|
|
177
|
+
allowedDirs.add(normalized.slice(0, lastSlash));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Analyze each violation and check if it's a "reasonable" expansion
|
|
181
|
+
const pathsToAdd = [];
|
|
182
|
+
const notAllowedViolations = violations.filter(v => v.violation === 'not_in_allowed');
|
|
183
|
+
for (const violation of notAllowedViolations) {
|
|
184
|
+
const normalizedFile = normalizePath(violation.file);
|
|
185
|
+
const lastSlash = normalizedFile.lastIndexOf('/');
|
|
186
|
+
const fileDir = lastSlash > 0 ? normalizedFile.slice(0, lastSlash) : '';
|
|
187
|
+
const fileName = lastSlash > 0 ? normalizedFile.slice(lastSlash + 1) : normalizedFile;
|
|
188
|
+
// Check if file is in a directory we already allow (sibling file)
|
|
189
|
+
const isSiblingFile = allowedDirs.has(fileDir);
|
|
190
|
+
// Check if file is a related type (types file, test file, index file)
|
|
191
|
+
const isRelatedFile = fileName.endsWith('.d.ts') ||
|
|
192
|
+
fileName.includes('.test.') ||
|
|
193
|
+
fileName.includes('.spec.') ||
|
|
194
|
+
fileName === 'index.ts' ||
|
|
195
|
+
fileName === 'index.tsx' ||
|
|
196
|
+
fileName === 'index.js' ||
|
|
197
|
+
fileName === 'types.ts' ||
|
|
198
|
+
fileName === 'types.tsx';
|
|
199
|
+
// Check if file is in an allowed subdirectory
|
|
200
|
+
const isSubdirectory = [...allowedDirs].some(dir => fileDir.startsWith(dir + '/'));
|
|
201
|
+
// Check if file is a root-level config (vitest.config.ts, tsconfig.json, etc.)
|
|
202
|
+
const isRootConfig = !fileDir && /^(vitest|vite|jest|tsconfig|eslint|prettier|babel|rollup|webpack|next|tailwind)\b/.test(fileName);
|
|
203
|
+
if (isSiblingFile || isRelatedFile || isSubdirectory || isRootConfig) {
|
|
204
|
+
pathsToAdd.push(normalizedFile);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Second pass: if some violations remain, check if they share a common
|
|
208
|
+
// top-level module with the allowed paths (e.g. both under src/api/).
|
|
209
|
+
// This handles legitimate cross-directory refactors like renames.
|
|
210
|
+
if (pathsToAdd.length < notAllowedViolations.length) {
|
|
211
|
+
const allowedTopDirs = new Set();
|
|
212
|
+
for (const dir of allowedDirs) {
|
|
213
|
+
const parts = dir.split('/');
|
|
214
|
+
// Add the root directory (e.g. "src" from "src/lib/utils")
|
|
215
|
+
if (parts.length >= 1) {
|
|
216
|
+
allowedTopDirs.add(parts[0]);
|
|
217
|
+
}
|
|
218
|
+
// Also add first two segments as the "module" (e.g. "src/lib")
|
|
219
|
+
if (parts.length >= 2) {
|
|
220
|
+
allowedTopDirs.add(parts.slice(0, 2).join('/'));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Cross-package awareness: if any packages/*/src path is allowed,
|
|
224
|
+
// treat other packages/*/src paths as related (monorepo siblings)
|
|
225
|
+
const hasPackagesDir = [...allowedDirs].some(d => d.startsWith('packages/'));
|
|
226
|
+
if (hasPackagesDir) {
|
|
227
|
+
allowedTopDirs.add('packages');
|
|
228
|
+
}
|
|
229
|
+
for (const violation of notAllowedViolations) {
|
|
230
|
+
const normalizedFile = normalizePath(violation.file);
|
|
231
|
+
if (pathsToAdd.includes(normalizedFile))
|
|
232
|
+
continue;
|
|
233
|
+
const fileParts = normalizedFile.split('/');
|
|
234
|
+
const fileTopDir = fileParts.length >= 2
|
|
235
|
+
? fileParts.slice(0, 2).join('/')
|
|
236
|
+
: fileParts[0];
|
|
237
|
+
if (allowedTopDirs.has(fileTopDir) || allowedTopDirs.has(fileParts[0])) {
|
|
238
|
+
pathsToAdd.push(normalizedFile);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Check if we have too many expansions
|
|
243
|
+
if (pathsToAdd.length === 0) {
|
|
244
|
+
return {
|
|
245
|
+
canExpand: false,
|
|
246
|
+
expandedPaths: currentAllowedPaths,
|
|
247
|
+
addedPaths: [],
|
|
248
|
+
reason: `Cannot expand: ${notAllowedViolations.length} file(s) in unrelated directories`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (pathsToAdd.length > maxExpansions) {
|
|
252
|
+
return {
|
|
253
|
+
canExpand: false,
|
|
254
|
+
expandedPaths: currentAllowedPaths,
|
|
255
|
+
addedPaths: [],
|
|
256
|
+
reason: `Cannot expand: ${pathsToAdd.length} files need expansion (max: ${maxExpansions})`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// Merge new paths with existing
|
|
260
|
+
const expandedPaths = [...currentAllowedPaths, ...pathsToAdd];
|
|
261
|
+
const uniquePaths = [...new Set(expandedPaths)];
|
|
262
|
+
return {
|
|
263
|
+
canExpand: true,
|
|
264
|
+
expandedPaths: uniquePaths,
|
|
265
|
+
addedPaths: pathsToAdd,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Parse git status --porcelain output to get changed file paths
|
|
270
|
+
*
|
|
271
|
+
* @param statusOutput - Output from `git status --porcelain`
|
|
272
|
+
* @returns List of file paths that were changed
|
|
273
|
+
*/
|
|
274
|
+
export function parseChangedFiles(statusOutput) {
|
|
275
|
+
if (!statusOutput.trim())
|
|
276
|
+
return [];
|
|
277
|
+
return statusOutput
|
|
278
|
+
.split('\n')
|
|
279
|
+
.filter(line => line.trim())
|
|
280
|
+
.map(line => {
|
|
281
|
+
// Format: XY filename or XY original -> renamed
|
|
282
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
283
|
+
const match = line.match(/^..\s+(.+?)(?:\s+->\s+(.+))?$/);
|
|
284
|
+
if (!match)
|
|
285
|
+
return null;
|
|
286
|
+
// Return the destination for renames, otherwise the filename
|
|
287
|
+
return match[2] || match[1];
|
|
288
|
+
})
|
|
289
|
+
.filter((f) => f !== null);
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/lib/scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selection parser for user input
|
|
3
|
+
*
|
|
4
|
+
* Parses selection strings like:
|
|
5
|
+
* - "1" → [0]
|
|
6
|
+
* - "1,3,5" → [0, 2, 4]
|
|
7
|
+
* - "1-3" → [0, 1, 2]
|
|
8
|
+
* - "1-3,5,7" → [0, 1, 2, 4, 6]
|
|
9
|
+
* - "all" → all indices
|
|
10
|
+
*
|
|
11
|
+
* Note: User input is 1-indexed, output is 0-indexed.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Parse a selection string into 0-indexed array indices
|
|
15
|
+
*
|
|
16
|
+
* @param input - Selection string (e.g., "1-3,5,7" or "all")
|
|
17
|
+
* @param maxIndex - Maximum valid index (0-indexed, exclusive)
|
|
18
|
+
* @returns Array of 0-indexed indices, sorted and deduplicated
|
|
19
|
+
* @throws Error for invalid input
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseSelection(input: string, maxIndex: number): number[];
|
|
22
|
+
/**
|
|
23
|
+
* Validate a selection string without parsing
|
|
24
|
+
*
|
|
25
|
+
* @returns true if valid, false otherwise
|
|
26
|
+
*/
|
|
27
|
+
export declare function isValidSelection(input: string, maxIndex: number): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Format a selection for display (1-indexed)
|
|
30
|
+
*
|
|
31
|
+
* @param indices - 0-indexed array indices
|
|
32
|
+
* @returns Human-readable string like "1, 2, 3" or "1-3"
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatSelection(indices: number[]): string;
|
|
35
|
+
//# sourceMappingURL=selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../src/lib/selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAoDxE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CA0BzD"}
|