cans-spec 0.3.0 → 0.5.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.
@@ -108,9 +108,22 @@ export function buildReadPlan(
108
108
  }
109
109
 
110
110
  const backRefFiles = new Set<string>();
111
+ // §26 step 3 forward-ref tier (QA-17 F28): the files the canonical home
112
+ // POINTS TO — targets of see: refs made from the home file — connect at 40.
113
+ // (Back-refs, the files that point AT the home, stay 60; a file that is
114
+ // both keeps the higher tier.)
115
+ const forwardFiles = new Set<string>();
111
116
  if (home !== null) {
112
117
  for (const bp of backPointers) {
113
118
  if (targetMatchesKey(bp.toFile, home.file)) backRefFiles.add(bp.fromFile);
119
+ if (bp.fromFile === home.file) {
120
+ for (const key of allFiles.keys()) {
121
+ if (targetMatchesKey(bp.toFile, key)) {
122
+ forwardFiles.add(key);
123
+ break;
124
+ }
125
+ }
126
+ }
114
127
  }
115
128
  }
116
129
  for (const key of allFiles.keys()) {
@@ -119,6 +132,10 @@ export function buildReadPlan(
119
132
  items.set(key, { file: key, anchor: null, reason: 'see: back-ref', score: 60, estTokens: tokens(key), rank: 2 });
120
133
  continue;
121
134
  }
135
+ if (forwardFiles.has(key)) {
136
+ items.set(key, { file: key, anchor: null, reason: 'forward ref', score: 40, estTokens: tokens(key), rank: 3 });
137
+ continue;
138
+ }
122
139
  const mentions = flattenNodes(allFiles.get(key)!).some(n => n.text.toLowerCase().includes(lc));
123
140
  if (mentions) {
124
141
  items.set(key, { file: key, anchor: null, reason: 'mentions concept', score: 20, estTokens: tokens(key), rank: 3 });
@@ -182,10 +199,14 @@ export function buildReadPlan(
182
199
  const plan: BudgetReadPlanItem[] = [];
183
200
  const skipped: string[] = [];
184
201
  let totalTokens = 0;
185
- let cut = false;
202
+ // §26 step 4 (issue #16): best-effort greedy packing. Items are walked in
203
+ // score order and each item that fits under the remaining budget is
204
+ // planned. An item that does not fit is skipped (listed in `skipped`) but
205
+ // does NOT cut the walk — cheaper lower-scored items that still fit are
206
+ // considered, so a limit below the canonical home (score 100) can still
207
+ // afford a cheaper back-ref (score 60) instead of yielding plan: [].
186
208
  for (const item of sorted) {
187
- if (cut || totalTokens + item.estTokens > budgetLimit) {
188
- cut = true;
209
+ if (totalTokens + item.estTokens > budgetLimit) {
189
210
  skipped.push(item.file);
190
211
  continue;
191
212
  }
@@ -198,6 +219,16 @@ export function buildReadPlan(
198
219
  for (const key of allFiles.keys()) {
199
220
  if (!items.has(key)) skipped.push(key);
200
221
  }
222
+ // §26 step 4 (QA-17 F25): skipped lists EVERY file not in the plan — active
223
+ // task files are in budget scope (§22), so a task file with no connection
224
+ // to the concept (never scored into `items`) is listed too, never
225
+ // invisible. Planned-but-unaffordable task files already landed in skipped
226
+ // via the packing loop above.
227
+ if (activeTaskPaths !== undefined) {
228
+ for (const taskPath of activeTaskPaths) {
229
+ if (!items.has(taskPath)) skipped.push(taskPath);
230
+ }
231
+ }
201
232
  skipped.sort();
202
233
 
203
234
  const usagePercent = budgetLimit > 0
package/src/types.ts CHANGED
@@ -33,6 +33,12 @@ export interface BackPointer {
33
33
  fromFile: string;
34
34
  fromLine: number;
35
35
  toFile: string;
36
+ /** The anchor side of the back-pointer. For graph-built back-pointers
37
+ * (buildRefGraph): the ref's raw anchor token, null for file-level refs.
38
+ * For extracted `<!-- ref-by: ... -->` comments (extractBackPointers,
39
+ * issue #19): the text of the node whose bullet line carries the comment
40
+ * (INLINE form — a node mark), or null when the comment stands on its own
41
+ * line (STANDALONE form — a file-level mark). */
36
42
  toAnchor: string | null;
37
43
  }
38
44
 
@@ -48,6 +54,10 @@ export interface Issue {
48
54
  category: IssueCategory;
49
55
  message: string;
50
56
  suggestion?: string;
57
+ /** issue #41: machine-readable dotted rule key (e.g. "refs.broken.file",
58
+ * "structure.node_length.max") — stable vocabulary for report grouping and
59
+ * agent consumption. Optional so non-engine Issue constructors stay legal. */
60
+ rule?: string;
51
61
  }
52
62
 
53
63
  // ── Rules ──
@@ -160,6 +170,13 @@ export interface CheckResult extends CommandResult {
160
170
  errorCount: number;
161
171
  warningCount: number;
162
172
  backPointersUpdated: number;
173
+ /** Issue #11: spec-relative paths of the files --fix actually rewrote
174
+ * (sorted). Empty without --fix or when nothing needed a write; with a
175
+ * [file] filter only matching files can ever appear here. */
176
+ backPointersUpdatedFiles: string[];
177
+ /** issue #41: wall-clock duration of the whole checkWorkspace run,
178
+ * rounded to whole ms (0 for the static checkFail paths). */
179
+ elapsedMs: number;
163
180
  /** §22/§36: human-facing one-line summary of the active _rules.yaml limits (QA-02 F17). */
164
181
  rulesSummary?: string;
165
182
  }