@ikunin/sprintpilot 2.2.6 → 2.2.8
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.
|
@@ -56,7 +56,65 @@ function canonicalizeYaml(text) {
|
|
|
56
56
|
return `${lines.join('\n')}\n`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Directory names always pruned from fingerprint walks. These are
|
|
60
|
+
// regenerable build/cache artifacts that change between sessions for
|
|
61
|
+
// reasons unrelated to BMad state (Python bytecode, dependency
|
|
62
|
+
// installs, transpiler output, OS metadata). Without pruning, a single
|
|
63
|
+
// halt fingerprint can balloon to 100s of MB and a `.pyc` regen on
|
|
64
|
+
// resume produces spurious divergence prompts.
|
|
65
|
+
//
|
|
66
|
+
// Real-world trigger: a user's `_bmad-output/spikes/<name>/.venv/` was
|
|
67
|
+
// 794 MB; every halt entry captured every path inside it.
|
|
68
|
+
const FINGERPRINT_PRUNE_DIRS = new Set([
|
|
69
|
+
'.venv',
|
|
70
|
+
'venv',
|
|
71
|
+
'env',
|
|
72
|
+
'node_modules',
|
|
73
|
+
'__pycache__',
|
|
74
|
+
'.pytest_cache',
|
|
75
|
+
'.mypy_cache',
|
|
76
|
+
'.ruff_cache',
|
|
77
|
+
'.tox',
|
|
78
|
+
'.gradle',
|
|
79
|
+
'target',
|
|
80
|
+
'dist',
|
|
81
|
+
'build',
|
|
82
|
+
'.next',
|
|
83
|
+
'.nuxt',
|
|
84
|
+
'.cache',
|
|
85
|
+
'.parcel-cache',
|
|
86
|
+
'.turbo',
|
|
87
|
+
'.git',
|
|
88
|
+
'.svn',
|
|
89
|
+
'.hg',
|
|
90
|
+
'.idea',
|
|
91
|
+
'.vscode',
|
|
92
|
+
// BMad/Sprintpilot internal — worktrees aren't fingerprinted; they're
|
|
93
|
+
// captured separately via context.worktreeScanner.
|
|
94
|
+
'.worktrees',
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
// File suffixes always pruned. Generated / binary content that changes
|
|
98
|
+
// for non-state reasons.
|
|
99
|
+
const FINGERPRINT_PRUNE_SUFFIXES = [
|
|
100
|
+
'.pyc',
|
|
101
|
+
'.pyo',
|
|
102
|
+
'.pyd',
|
|
103
|
+
'.so',
|
|
104
|
+
'.o',
|
|
105
|
+
'.class',
|
|
106
|
+
'.DS_Store',
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
// Hard cap on number of entries in the fingerprint tree. Defends against
|
|
110
|
+
// pathological cases where prune lists don't catch a large embedded
|
|
111
|
+
// dependency tree. When hit, the walk stops and `out[__truncated__]`
|
|
112
|
+
// is set so callers know the fingerprint is incomplete (treated as
|
|
113
|
+
// divergent on diff to avoid false-negative resume).
|
|
114
|
+
const FINGERPRINT_MAX_ENTRIES = 5000;
|
|
115
|
+
|
|
59
116
|
function walkTree(fs, root, out, relBase) {
|
|
117
|
+
if (out.__truncated__) return;
|
|
60
118
|
let entries;
|
|
61
119
|
try {
|
|
62
120
|
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
@@ -64,14 +122,31 @@ function walkTree(fs, root, out, relBase) {
|
|
|
64
122
|
return;
|
|
65
123
|
}
|
|
66
124
|
for (const ent of entries) {
|
|
125
|
+
if (out.__truncated__) return;
|
|
126
|
+
if (FINGERPRINT_PRUNE_DIRS.has(ent.name)) continue;
|
|
67
127
|
const full = path.join(root, ent.name);
|
|
68
128
|
const rel = path.join(relBase, ent.name);
|
|
69
129
|
if (ent.isDirectory()) {
|
|
70
130
|
walkTree(fs, full, out, rel);
|
|
71
131
|
} else if (ent.isFile()) {
|
|
132
|
+
let prune = false;
|
|
133
|
+
for (const sfx of FINGERPRINT_PRUNE_SUFFIXES) {
|
|
134
|
+
if (ent.name.endsWith(sfx)) {
|
|
135
|
+
prune = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (prune) continue;
|
|
72
140
|
try {
|
|
73
141
|
const st = fs.statSync(full);
|
|
74
142
|
out[rel.split(path.sep).join('/')] = st.size;
|
|
143
|
+
// -1 for the future __truncated__ marker; -2 below for the actual count
|
|
144
|
+
// (avoid counting the marker itself).
|
|
145
|
+
const count = Object.keys(out).length - (out.__truncated__ ? 1 : 0);
|
|
146
|
+
if (count >= FINGERPRINT_MAX_ENTRIES) {
|
|
147
|
+
out.__truncated__ = true;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
75
150
|
} catch (_e) {
|
|
76
151
|
// ignore unreadable
|
|
77
152
|
}
|
|
@@ -157,7 +157,17 @@ function verifyCreateStory(state, _out, ctx) {
|
|
|
157
157
|
else {
|
|
158
158
|
const text = readFileSafe(ctx.fs, state.story_file_path);
|
|
159
159
|
const fm = frontMatter(text);
|
|
160
|
-
|
|
160
|
+
// Escape hatch: when the LLM sends verify_override with evidence
|
|
161
|
+
// {acknowledge_missing_front_matter: true, decision_log_ref: '...'},
|
|
162
|
+
// skip the front-matter check ONLY for this verification call. AC +
|
|
163
|
+
// Tasks checks still run. Auditable via the verify_override ledger
|
|
164
|
+
// entry which captures evidence verbatim. Used when bmad-create-story
|
|
165
|
+
// can't or won't regenerate front-matter (e.g., legacy story files
|
|
166
|
+
// in repos that pre-date the front-matter convention and have a
|
|
167
|
+
// body the skill wants to preserve).
|
|
168
|
+
const override = ctx.augmented || {};
|
|
169
|
+
const ackMissingFm = override && override.acknowledge_missing_front_matter === true;
|
|
170
|
+
if (!fm && !ackMissingFm) issues.push('story file missing YAML front-matter');
|
|
161
171
|
// AC presence — look for "## Acceptance Criteria" section with at least one bullet.
|
|
162
172
|
if (text && !/##\s+Acceptance Criteria[\s\S]*?\n-\s+/.test(text)) {
|
|
163
173
|
issues.push('Acceptance Criteria section missing or empty');
|
package/package.json
CHANGED