@deftai/directive-core 0.56.1 → 0.56.2

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.
@@ -63,6 +63,10 @@ const SKIP_DIRS = new Set([
63
63
  "dist",
64
64
  "tests",
65
65
  ".deft-cache",
66
+ // node_modules holds third-party markdown (dependency READMEs) that is not
67
+ // framework content; scanning it produced volatile, version-pinned cases that
68
+ // broke CI when dependency versions drifted (#1993 follow-up).
69
+ "node_modules",
66
70
  ]);
67
71
  const MIGRATION_ARTIFACT_TREES = ["vbrief/migration"];
68
72
  export function isMigrationArtifact(rel) {
@@ -148,6 +148,32 @@ function metaFetchedAt(metaPath) {
148
148
  return null;
149
149
  }
150
150
  }
151
+ /**
152
+ * #1991: report whether a cache entry's upstream issue is terminal-closed.
153
+ *
154
+ * Reads the sibling raw.json (the cached issue payload) and returns true only
155
+ * when its `state` is positively "closed". Unknown / missing state returns
156
+ * false so we never exclude an entry we are not sure about. Closed issues are
157
+ * terminal: their `fetched_at` age is irrelevant to dispatch-queue freshness,
158
+ * and `cache:fetch-all --force` (open-only enumeration) can never refresh them,
159
+ * so the age gate must not wedge on them.
160
+ */
161
+ function metaEntryIsTerminalClosed(metaPath) {
162
+ const rawPath = join(metaPath, "..", "raw.json");
163
+ if (!existsSync(rawPath))
164
+ return false;
165
+ try {
166
+ const raw = JSON.parse(readFileSync(rawPath, "utf8"));
167
+ if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
168
+ const state = raw.state;
169
+ return typeof state === "string" && state.toLowerCase() === "closed";
170
+ }
171
+ }
172
+ catch {
173
+ /* unknown state -> keep in scope */
174
+ }
175
+ return false;
176
+ }
151
177
  function loadScopeRules(projectRoot) {
152
178
  const defPath = join(projectRoot, "vbrief", "PROJECT-DEFINITION.vbrief.json");
153
179
  if (!existsSync(defPath))
@@ -398,10 +424,16 @@ export function evaluate(projectRoot, options = {}) {
398
424
  allMetaPaths.length > 0 &&
399
425
  scopedMetaPaths.length === 0 &&
400
426
  candState === "populated";
401
- // Step 5: Oldest in-scope entry age + drift probe (#1886)
427
+ // Step 5: Oldest in-scope entry age + drift probe (#1886).
428
+ // #1991: judge age over OPEN entries only. Terminal-closed entries are
429
+ // excluded because `cache:fetch-all --force` (open-only enumeration) can never
430
+ // refresh their `fetched_at`, so including them would let a stale closed entry
431
+ // wedge the gate with no working recovery command.
432
+ const rawCandidatePaths = scopedMetaPaths.length > 0 ? scopedMetaPaths : allMetaPaths;
433
+ const ageCandidatePaths = rawCandidatePaths.filter((p) => !metaEntryIsTerminalClosed(p));
402
434
  let minFetchedAt = null;
403
435
  let minMetaPath = null;
404
- for (const p of scopedMetaPaths.length > 0 ? scopedMetaPaths : allMetaPaths) {
436
+ for (const p of ageCandidatePaths) {
405
437
  const ts = metaFetchedAt(p);
406
438
  if (ts !== null && (minFetchedAt === null || ts < minFetchedAt)) {
407
439
  minFetchedAt = ts;
@@ -411,7 +443,11 @@ export function evaluate(projectRoot, options = {}) {
411
443
  const now = nowFn();
412
444
  const ageMs = minFetchedAt !== null ? now.getTime() - minFetchedAt.getTime() : Infinity;
413
445
  const ageH = ageMs / (1000 * 3600);
414
- const stale = ageH > maxAgeHours;
446
+ // When every candidate is terminal-closed (non-empty input, no open entry to
447
+ // judge) there is nothing open whose age can be stale -> fresh. The truly
448
+ // empty-cache case (no entries at all) preserves its prior staleness result.
449
+ const allCandidatesClosed = minFetchedAt === null && rawCandidatePaths.length > 0;
450
+ const stale = allCandidatesClosed ? false : ageH > maxAgeHours;
415
451
  // Step 5b: --allow-stale bypass
416
452
  if (stale && allowStale) {
417
453
  const warning = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.56.1",
3
+ "version": "0.56.2",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -189,6 +189,10 @@
189
189
  "./init-deposit": {
190
190
  "types": "./dist/init-deposit/index.d.ts",
191
191
  "default": "./dist/init-deposit/index.js"
192
+ },
193
+ "./dist/*.js": {
194
+ "types": "./dist/*.d.ts",
195
+ "default": "./dist/*.js"
192
196
  }
193
197
  },
194
198
  "files": [
@@ -205,8 +209,8 @@
205
209
  "provenance": true
206
210
  },
207
211
  "dependencies": {
208
- "@deftai/directive-content": "^0.56.1",
209
- "@deftai/directive-types": "^0.56.1"
212
+ "@deftai/directive-content": "^0.56.2",
213
+ "@deftai/directive-types": "^0.56.2"
210
214
  },
211
215
  "scripts": {
212
216
  "build": "tsc -b",