@deftai/directive-core 0.95.0 → 0.96.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/dist/cache/archive.d.ts +134 -0
- package/dist/cache/archive.js +630 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/cache/main.js +298 -1
- package/dist/content-contracts/skills/helpers.d.ts +1 -1
- package/dist/content-contracts/skills/helpers.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/hygiene.d.ts +1 -1
- package/dist/init-deposit/hygiene.js +16 -4
- package/dist/init-deposit/scaffold.js +6 -5
- package/dist/parent-turn-shape/evaluate.d.ts +84 -0
- package/dist/parent-turn-shape/evaluate.js +353 -0
- package/dist/parent-turn-shape/index.d.ts +8 -0
- package/dist/parent-turn-shape/index.js +8 -0
- package/dist/review-monitor/constants.js +3 -2
- package/dist/review-monitor/tier-detection.d.ts +7 -3
- package/dist/review-monitor/tier-detection.js +18 -1
- package/dist/review-monitor/verify.js +18 -0
- package/dist/scope/index.d.ts +2 -0
- package/dist/scope/index.js +2 -0
- package/dist/scope/main.d.ts +10 -0
- package/dist/scope/main.js +109 -24
- package/dist/scope/promote-from-issue.d.ts +49 -0
- package/dist/scope/promote-from-issue.js +367 -0
- package/dist/scope/promote-path.d.ts +39 -0
- package/dist/scope/promote-path.js +105 -0
- package/dist/swarm/routing.d.ts +4 -2
- package/dist/swarm/routing.js +26 -4
- package/dist/triage/actions/index.js +62 -2
- package/dist/triage/actions/types.d.ts +8 -1
- package/dist/triage/author-filter.d.ts +51 -0
- package/dist/triage/author-filter.js +152 -0
- package/dist/triage/classify/index.d.ts +2 -2
- package/dist/triage/classify/index.js +2 -2
- package/dist/triage/classify/label-mirror.d.ts +68 -5
- package/dist/triage/classify/label-mirror.js +261 -31
- package/dist/triage/help/registry-data.d.ts +49 -38
- package/dist/triage/help/registry-data.js +115 -40
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/render.d.ts +2 -0
- package/dist/triage/queue/render.js +6 -0
- package/package.json +7 -3
package/dist/cache/main.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { latestDecisions, readAuditLog } from "../triage/actions/candidates-log.js";
|
|
4
|
+
import { resolveCandidatesLogPath } from "../triage/cache-path.js";
|
|
5
|
+
import { archiveClosedEntries, DEFAULT_ARCHIVE_OLDER_THAN_DAYS, listArchivedEntries, restoreFromArchive, } from "./archive.js";
|
|
3
6
|
import { ALLOWED_SOURCES, DEFAULT_BATCH_SIZE, DEFAULT_DELAY_MS, DEFAULT_PRUNE_OLDER_THAN_DAYS, } from "./constants.js";
|
|
4
7
|
import { CacheCapBreachedError, CacheError, CacheFetchError, CacheNotFoundError, CacheValidationError, } from "./errors.js";
|
|
5
8
|
import { cacheFetchAll, cacheRefreshClosed } from "./fetch.js";
|
|
@@ -8,7 +11,7 @@ import { cacheGet, cacheInvalidate, cachePrune, cachePruneToCap, cachePut } from
|
|
|
8
11
|
import { resolveCaps } from "./quota.js";
|
|
9
12
|
import { clearTaskCache } from "./task-cache/store.js";
|
|
10
13
|
function usage() {
|
|
11
|
-
process.stderr.write("usage: cache [-h] {put,get,invalidate,fetch-all,prune,clear} ...\n");
|
|
14
|
+
process.stderr.write("usage: cache [-h] {put,get,invalidate,fetch-all,prune,clear,archive-closed,archive-list,restore-from-archive} ...\n");
|
|
12
15
|
}
|
|
13
16
|
function normaliseLabelFilter(raw) {
|
|
14
17
|
if (!raw || raw.length === 0)
|
|
@@ -309,6 +312,293 @@ function cmdPrune(args) {
|
|
|
309
312
|
process.stdout.write(`${pythonJsonPretty(payload)}\n`);
|
|
310
313
|
return 0;
|
|
311
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Reversible closed-entry archive (#1137). Operator-only; not TTL prune.
|
|
317
|
+
* Distinct from `cache prune` (hard-delete by expires_at).
|
|
318
|
+
*/
|
|
319
|
+
function cmdArchiveClosed(args) {
|
|
320
|
+
let olderThanDays = DEFAULT_ARCHIVE_OLDER_THAN_DAYS;
|
|
321
|
+
let source = "github-issue";
|
|
322
|
+
let repo;
|
|
323
|
+
let dryRun = false;
|
|
324
|
+
let json = false;
|
|
325
|
+
let terminalDecisionOnly = false;
|
|
326
|
+
let projectRoot;
|
|
327
|
+
let cacheRoot;
|
|
328
|
+
let reason;
|
|
329
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
330
|
+
const arg = args[i];
|
|
331
|
+
if (arg === "--older-than-days") {
|
|
332
|
+
olderThanDays = Number.parseInt(args[i + 1] ?? "", 10);
|
|
333
|
+
i += 1;
|
|
334
|
+
}
|
|
335
|
+
else if (arg === "--source") {
|
|
336
|
+
source = args[i + 1] ?? "github-issue";
|
|
337
|
+
i += 1;
|
|
338
|
+
}
|
|
339
|
+
else if (arg === "--repo") {
|
|
340
|
+
repo = args[i + 1];
|
|
341
|
+
i += 1;
|
|
342
|
+
}
|
|
343
|
+
else if (arg === "--dry-run") {
|
|
344
|
+
dryRun = true;
|
|
345
|
+
}
|
|
346
|
+
else if (arg === "--json") {
|
|
347
|
+
json = true;
|
|
348
|
+
}
|
|
349
|
+
else if (arg === "--terminal-decision-only") {
|
|
350
|
+
terminalDecisionOnly = true;
|
|
351
|
+
}
|
|
352
|
+
else if (arg === "--project-root") {
|
|
353
|
+
projectRoot = args[i + 1];
|
|
354
|
+
i += 1;
|
|
355
|
+
}
|
|
356
|
+
else if (arg === "--cache-root") {
|
|
357
|
+
cacheRoot = args[i + 1];
|
|
358
|
+
i += 1;
|
|
359
|
+
}
|
|
360
|
+
else if (arg === "--reason") {
|
|
361
|
+
reason = args[i + 1];
|
|
362
|
+
i += 1;
|
|
363
|
+
}
|
|
364
|
+
else if (arg === "-h" || arg === "--help") {
|
|
365
|
+
process.stdout.write("usage: cache archive-closed [--dry-run] [--older-than-days 30] [--repo OWNER/NAME]\n" +
|
|
366
|
+
" [--source github-issue] [--json] [--terminal-decision-only]\n" +
|
|
367
|
+
" [--project-root PATH] [--cache-root PATH] [--reason TEXT]\n" +
|
|
368
|
+
"\n" +
|
|
369
|
+
"Reversible archive of closed github-issue cache entries (not TTL cache:prune).\n" +
|
|
370
|
+
"Skips issues still referenced by xbrief/{proposed,pending,active}.\n" +
|
|
371
|
+
"Also available as: task triage:cache-archive\n");
|
|
372
|
+
return 0;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
throw new CacheError(`unexpected argument: ${arg}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const resolvedProjectRoot = projectRoot ?? process.cwd();
|
|
379
|
+
const resolvedCacheRoot = cacheRoot ?? (projectRoot !== undefined ? resolve(projectRoot, ".deft-cache") : undefined);
|
|
380
|
+
let latestDecisionMap = null;
|
|
381
|
+
if (terminalDecisionOnly) {
|
|
382
|
+
try {
|
|
383
|
+
const logPath = resolveCandidatesLogPath(resolvedProjectRoot);
|
|
384
|
+
latestDecisionMap = latestDecisions(readAuditLog(logPath, repo ?? null));
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
latestDecisionMap = new Map();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const result = archiveClosedEntries({
|
|
391
|
+
olderThanDays,
|
|
392
|
+
source,
|
|
393
|
+
repo: repo ?? null,
|
|
394
|
+
dryRun,
|
|
395
|
+
terminalDecisionOnly,
|
|
396
|
+
latestDecisions: latestDecisionMap,
|
|
397
|
+
projectRoot: resolvedProjectRoot,
|
|
398
|
+
cacheRoot: resolvedCacheRoot,
|
|
399
|
+
reason,
|
|
400
|
+
});
|
|
401
|
+
const payload = {
|
|
402
|
+
mode: "archive-closed",
|
|
403
|
+
note: "Reversible closed-entry archive; distinct from cache:prune (TTL hard-delete).",
|
|
404
|
+
dry_run: result.dryRun,
|
|
405
|
+
older_than_days: result.olderThanDays,
|
|
406
|
+
source: result.source,
|
|
407
|
+
repo: result.repo,
|
|
408
|
+
terminal_decision_only: terminalDecisionOnly,
|
|
409
|
+
archived_count: result.archivedCount,
|
|
410
|
+
skipped_count: result.skippedCount,
|
|
411
|
+
archived: result.archived.map((c) => ({
|
|
412
|
+
key: c.key,
|
|
413
|
+
live_dir: c.liveDir,
|
|
414
|
+
archive_dir: c.archiveDir,
|
|
415
|
+
age_basis: c.ageBasis,
|
|
416
|
+
closed_at: c.closedAt,
|
|
417
|
+
pre_archive_decision: c.preArchiveDecision,
|
|
418
|
+
})),
|
|
419
|
+
skipped: result.skipped.map((s) => ({
|
|
420
|
+
key: s.key,
|
|
421
|
+
reason: s.reason,
|
|
422
|
+
detail: s.detail ?? null,
|
|
423
|
+
})),
|
|
424
|
+
};
|
|
425
|
+
if (json) {
|
|
426
|
+
process.stdout.write(`${pythonJsonPretty(payload)}\n`);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
process.stdout.write(`cache:archive-closed dry_run=${pythonBool(result.dryRun)} archived=${result.archivedCount} skipped=${result.skippedCount} older_than_days=${result.olderThanDays}\n`);
|
|
430
|
+
for (const c of result.archived) {
|
|
431
|
+
process.stdout.write(` archived ${c.key} -> ${c.archiveDir}\n`);
|
|
432
|
+
}
|
|
433
|
+
for (const s of result.skipped) {
|
|
434
|
+
process.stdout.write(` skipped ${s.key} (${s.reason}${s.detail ? `: ${s.detail}` : ""})\n`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return 0;
|
|
438
|
+
}
|
|
439
|
+
function cmdArchiveList(args) {
|
|
440
|
+
let source = "github-issue";
|
|
441
|
+
let repo;
|
|
442
|
+
let since;
|
|
443
|
+
let limit;
|
|
444
|
+
let format = "text";
|
|
445
|
+
let cacheRoot;
|
|
446
|
+
let projectRoot;
|
|
447
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
448
|
+
const arg = args[i];
|
|
449
|
+
if (arg === "--source") {
|
|
450
|
+
source = args[i + 1] ?? "github-issue";
|
|
451
|
+
i += 1;
|
|
452
|
+
}
|
|
453
|
+
else if (arg === "--repo") {
|
|
454
|
+
repo = args[i + 1];
|
|
455
|
+
i += 1;
|
|
456
|
+
}
|
|
457
|
+
else if (arg === "--since") {
|
|
458
|
+
since = args[i + 1];
|
|
459
|
+
i += 1;
|
|
460
|
+
}
|
|
461
|
+
else if (arg === "--limit") {
|
|
462
|
+
limit = Number.parseInt(args[i + 1] ?? "", 10);
|
|
463
|
+
i += 1;
|
|
464
|
+
}
|
|
465
|
+
else if (arg === "--format") {
|
|
466
|
+
format = args[i + 1] ?? "text";
|
|
467
|
+
i += 1;
|
|
468
|
+
}
|
|
469
|
+
else if (arg === "--format=json" || arg === "--json") {
|
|
470
|
+
format = "json";
|
|
471
|
+
}
|
|
472
|
+
else if (arg?.startsWith("--format=")) {
|
|
473
|
+
format = arg.slice("--format=".length);
|
|
474
|
+
}
|
|
475
|
+
else if (arg === "--cache-root") {
|
|
476
|
+
cacheRoot = args[i + 1];
|
|
477
|
+
i += 1;
|
|
478
|
+
}
|
|
479
|
+
else if (arg === "--project-root") {
|
|
480
|
+
projectRoot = args[i + 1];
|
|
481
|
+
i += 1;
|
|
482
|
+
}
|
|
483
|
+
else if (arg === "-h" || arg === "--help") {
|
|
484
|
+
process.stdout.write("usage: cache archive-list [--repo OWNER/NAME] [--since ISO] [--limit N]\n" +
|
|
485
|
+
" [--format=json|text] [--source github-issue] [--cache-root PATH]\n" +
|
|
486
|
+
"Also available as: task triage:archive-list\n");
|
|
487
|
+
return 0;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
throw new CacheError(`unexpected argument: ${arg}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const resolvedCacheRoot = cacheRoot ?? (projectRoot !== undefined ? resolve(projectRoot, ".deft-cache") : undefined);
|
|
494
|
+
const result = listArchivedEntries({
|
|
495
|
+
source,
|
|
496
|
+
repo: repo ?? null,
|
|
497
|
+
since: since ?? null,
|
|
498
|
+
limit: limit ?? null,
|
|
499
|
+
cacheRoot: resolvedCacheRoot,
|
|
500
|
+
});
|
|
501
|
+
if (format === "json") {
|
|
502
|
+
process.stdout.write(`${pythonJsonPretty({
|
|
503
|
+
source: result.source,
|
|
504
|
+
repo: result.repo,
|
|
505
|
+
count: result.count,
|
|
506
|
+
entries: result.entries.map((e) => ({
|
|
507
|
+
key: e.key,
|
|
508
|
+
archived_at: e.archivedAt,
|
|
509
|
+
archive_dir: e.archiveDir,
|
|
510
|
+
meta: e.meta,
|
|
511
|
+
})),
|
|
512
|
+
})}\n`);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
process.stdout.write(`cache:archive-list count=${result.count}\n`);
|
|
516
|
+
for (const e of result.entries) {
|
|
517
|
+
process.stdout.write(` ${e.key} archived_at=${e.archivedAt}\n`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
function cmdRestoreFromArchive(args) {
|
|
523
|
+
let source = "github-issue";
|
|
524
|
+
let key;
|
|
525
|
+
let issue;
|
|
526
|
+
let repo;
|
|
527
|
+
let force = false;
|
|
528
|
+
let cacheRoot;
|
|
529
|
+
let projectRoot;
|
|
530
|
+
let json = false;
|
|
531
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
532
|
+
const arg = args[i];
|
|
533
|
+
if (arg === "--source") {
|
|
534
|
+
source = args[i + 1] ?? "github-issue";
|
|
535
|
+
i += 1;
|
|
536
|
+
}
|
|
537
|
+
else if (arg === "--key") {
|
|
538
|
+
key = args[i + 1];
|
|
539
|
+
i += 1;
|
|
540
|
+
}
|
|
541
|
+
else if (arg === "--issue") {
|
|
542
|
+
const rawIssue = args[i + 1] ?? "";
|
|
543
|
+
if (!/^[1-9]\d*$/.test(rawIssue)) {
|
|
544
|
+
throw new CacheError(`--issue must be a positive integer (got ${JSON.stringify(rawIssue)})`);
|
|
545
|
+
}
|
|
546
|
+
issue = Number.parseInt(rawIssue, 10);
|
|
547
|
+
i += 1;
|
|
548
|
+
}
|
|
549
|
+
else if (arg === "--repo") {
|
|
550
|
+
repo = args[i + 1];
|
|
551
|
+
i += 1;
|
|
552
|
+
}
|
|
553
|
+
else if (arg === "--force") {
|
|
554
|
+
force = true;
|
|
555
|
+
}
|
|
556
|
+
else if (arg === "--json") {
|
|
557
|
+
json = true;
|
|
558
|
+
}
|
|
559
|
+
else if (arg === "--cache-root") {
|
|
560
|
+
cacheRoot = args[i + 1];
|
|
561
|
+
i += 1;
|
|
562
|
+
}
|
|
563
|
+
else if (arg === "--project-root") {
|
|
564
|
+
projectRoot = args[i + 1];
|
|
565
|
+
i += 1;
|
|
566
|
+
}
|
|
567
|
+
else if (arg === "-h" || arg === "--help") {
|
|
568
|
+
process.stdout.write("usage: cache restore-from-archive -- --issue N --repo OWNER/NAME [--force]\n" +
|
|
569
|
+
" [--key owner/repo/N] [--source github-issue] [--json]\n" +
|
|
570
|
+
"Also available as: task triage:restore-from-archive\n");
|
|
571
|
+
return 0;
|
|
572
|
+
}
|
|
573
|
+
else if (!key && arg && !arg.startsWith("-") && arg.includes("/")) {
|
|
574
|
+
key = arg;
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
throw new CacheError(`unexpected argument: ${arg}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const resolvedCacheRoot = cacheRoot ?? (projectRoot !== undefined ? resolve(projectRoot, ".deft-cache") : undefined);
|
|
581
|
+
const result = restoreFromArchive({
|
|
582
|
+
source,
|
|
583
|
+
key,
|
|
584
|
+
issue,
|
|
585
|
+
repo: repo ?? null,
|
|
586
|
+
force,
|
|
587
|
+
cacheRoot: resolvedCacheRoot,
|
|
588
|
+
});
|
|
589
|
+
if (json) {
|
|
590
|
+
process.stdout.write(`${pythonJsonPretty(result)}\n`);
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
process.stdout.write(`cache:restore-from-archive status=${result.status} key=${result.key} live=${result.liveDir}\n`);
|
|
594
|
+
if (result.detail) {
|
|
595
|
+
process.stdout.write(` detail: ${result.detail}\n`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (result.status === "missing" || result.status === "conflict")
|
|
599
|
+
return 1;
|
|
600
|
+
return 0;
|
|
601
|
+
}
|
|
312
602
|
/** CLI entry point (mirrors `scripts/cache.py::main`). */
|
|
313
603
|
export function main(argv) {
|
|
314
604
|
if (argv.length === 0) {
|
|
@@ -332,6 +622,13 @@ export function main(argv) {
|
|
|
332
622
|
return cmdPrune(rest);
|
|
333
623
|
case "clear":
|
|
334
624
|
return cmdClear(rest);
|
|
625
|
+
case "archive-closed":
|
|
626
|
+
case "cache-archive":
|
|
627
|
+
return cmdArchiveClosed(rest);
|
|
628
|
+
case "archive-list":
|
|
629
|
+
return cmdArchiveList(rest);
|
|
630
|
+
case "restore-from-archive":
|
|
631
|
+
return cmdRestoreFromArchive(rest);
|
|
335
632
|
default:
|
|
336
633
|
usage();
|
|
337
634
|
process.stderr.write(`cache: error: argument cmd: invalid choice: '${cmd}'\n`);
|
|
@@ -21,7 +21,7 @@ export declare function readSkill(relPath: string): string;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare const SWARM_SKILL_REL = "skills/deft-directive-swarm/SKILL.md";
|
|
23
23
|
/** Stable load order: core phases, then host launch adapters, then ops. */
|
|
24
|
-
export declare const SWARM_REFERENCE_ORDER: readonly ["core-phase-0.md", "core-phase-1-2.md", "core-phase-3.md", "host-warp.md", "host-generic.md", "host-grok-build.md", "host-cursor.md", "host-openclaw.md", "core-phase-4.md", "core-phase-5-6.md", "core-ops.md"];
|
|
24
|
+
export declare const SWARM_REFERENCE_ORDER: readonly ["core-phase-0.md", "core-phase-1-2.md", "core-phase-3.md", "host-warp.md", "host-generic.md", "host-grok-build.md", "host-cursor.md", "host-claude-code.md", "host-openclaw.md", "core-phase-4.md", "core-phase-5-6.md", "core-ops.md"];
|
|
25
25
|
export declare function readSwarmSkillSurface(): string;
|
|
26
26
|
export declare function readAgentsMd(): string;
|
|
27
27
|
/** Slice the first `## Returning Sessions` section body out of AGENTS.md. */
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export * as lifecycle from "./lifecycle/index.js";
|
|
|
35
35
|
export * as metrics from "./metrics/index.js";
|
|
36
36
|
export * as orchestration from "./orchestration/index.js";
|
|
37
37
|
export * as packs from "./packs/index.js";
|
|
38
|
+
export * as parentTurnShape from "./parent-turn-shape/index.js";
|
|
38
39
|
export * as platform from "./platform/index.js";
|
|
39
40
|
export * as policy from "./policy/index.js";
|
|
40
41
|
export * as prClosingKeywords from "./pr-closing-keywords/index.js";
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ export * as lifecycle from "./lifecycle/index.js";
|
|
|
36
36
|
export * as metrics from "./metrics/index.js";
|
|
37
37
|
export * as orchestration from "./orchestration/index.js";
|
|
38
38
|
export * as packs from "./packs/index.js";
|
|
39
|
+
export * as parentTurnShape from "./parent-turn-shape/index.js";
|
|
39
40
|
export * as platform from "./platform/index.js";
|
|
40
41
|
export * as policy from "./policy/index.js";
|
|
41
42
|
export * as prClosingKeywords from "./pr-closing-keywords/index.js";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* and scope briefs are never installer-managed; if they reappear in
|
|
9
9
|
* `installerManagedMatchers()`, unit tests and deposit-time assert fail closed.
|
|
10
10
|
*
|
|
11
|
-
* Refs #1576, #1453, #1430, #3029, #3030.
|
|
11
|
+
* Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117.
|
|
12
12
|
*/
|
|
13
13
|
import { type InitDepositIo } from "./constants.js";
|
|
14
14
|
export declare const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* and scope briefs are never installer-managed; if they reappear in
|
|
9
9
|
* `installerManagedMatchers()`, unit tests and deposit-time assert fail closed.
|
|
10
10
|
*
|
|
11
|
-
* Refs #1576, #1453, #1430, #3029, #3030.
|
|
11
|
+
* Refs #1576, #1453, #1430, #3029, #3030, #3127, #3117.
|
|
12
12
|
*/
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
14
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
@@ -82,6 +82,16 @@ export function installerManagedMatchers() {
|
|
|
82
82
|
{ exact: CODEQL_CONFIG_REL },
|
|
83
83
|
{ exact: CORE_GUARD_WORKFLOW_REL },
|
|
84
84
|
{ exact: "Taskfile.yml" },
|
|
85
|
+
// Upgrade co-travel unit (#3127): npm pin + lock follow-through and the
|
|
86
|
+
// freshness live stamp (#3117) are part of a normal framework upgrade, not
|
|
87
|
+
// product feature work. Path-level allow for v1 (stricter “only
|
|
88
|
+
// @deftai/directive version changed” is a follow-on if needed).
|
|
89
|
+
// Mixing .deft/core/** with true app/product paths still fails (#1430).
|
|
90
|
+
{ exact: "package.json" },
|
|
91
|
+
{ exact: "package-lock.json" },
|
|
92
|
+
{ exact: "pnpm-lock.yaml" },
|
|
93
|
+
{ exact: "yarn.lock" },
|
|
94
|
+
{ exact: ".deft/GENERATION.json" },
|
|
85
95
|
// Legacy vbrief/ tree -- retained for not-yet-migrated consumers.
|
|
86
96
|
{ exact: "vbrief/.deft-version" },
|
|
87
97
|
{ exact: "vbrief/vbrief.md" },
|
|
@@ -476,9 +486,11 @@ export function printCommitGuidance(io, paths, staged) {
|
|
|
476
486
|
if (paths.length === 0)
|
|
477
487
|
return;
|
|
478
488
|
const addCmd = `git add ${paths.join(" ")}`;
|
|
479
|
-
io.printf("\nCommit hygiene (#1453, #1671): keep the framework
|
|
480
|
-
io.printf("Do NOT use `git add -A` -- mixing the payload with
|
|
489
|
+
io.printf("\nCommit hygiene (#1453, #1671, #3127): keep the framework upgrade in its OWN branch/PR.\n");
|
|
490
|
+
io.printf("Do NOT use `git add -A` -- mixing the payload with product/app files trips the\n");
|
|
481
491
|
io.printf("deft-core-guard CI check.\n");
|
|
492
|
+
io.printf("One upgrade PR MAY co-travel: .deft/core/** + installer-managed deposits + package.json\n");
|
|
493
|
+
io.printf("pin/lock + .deft/GENERATION.json. True app/product paths still require a separate PR.\n");
|
|
482
494
|
if (staged) {
|
|
483
495
|
io.printf("The installer already staged ONLY these framework + installer-managed paths:\n");
|
|
484
496
|
io.printf(` ${addCmd}\n`);
|
|
@@ -488,7 +500,7 @@ export function printCommitGuidance(io, paths, staged) {
|
|
|
488
500
|
io.printf(` ${addCmd}\n`);
|
|
489
501
|
}
|
|
490
502
|
io.printf("Then take the framework deposit through the full PR lifecycle so deft-core-guard\n");
|
|
491
|
-
io.printf("evaluates a clean, standalone PR:\n");
|
|
503
|
+
io.printf("evaluates a clean, standalone upgrade PR:\n");
|
|
492
504
|
io.printf(` 1. Branch: git switch -c ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
|
|
493
505
|
io.printf(' 2. Commit: git commit -m "chore(deft): update framework payload"\n');
|
|
494
506
|
io.printf(` 3. Push: git push -u origin ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
|
|
@@ -507,11 +507,12 @@ function coreGuardWorkflowContent() {
|
|
|
507
507
|
const baseSha = githubActionsExpr("github.event.pull_request.base.sha");
|
|
508
508
|
const headSha = githubActionsExpr("github.event.pull_request.head.sha");
|
|
509
509
|
return ("name: deft-core-guard\n\n" +
|
|
510
|
-
"# Deft framework guard (#1430): a single PR should not mix changes to the\n" +
|
|
511
|
-
"# vendored framework payload (.deft/core/**) with
|
|
512
|
-
"#
|
|
513
|
-
"#
|
|
514
|
-
"#
|
|
510
|
+
"# Deft framework guard (#1430 / #3127): a single PR should not mix changes to the\n" +
|
|
511
|
+
"# vendored framework payload (.deft/core/**) with true application/product files.\n" +
|
|
512
|
+
"# One upgrade PR MAY include deposit + installer-managed paths + package.json pin/lock\n" +
|
|
513
|
+
"# + .deft/GENERATION.json. Framework updates come from `deft update` / install and should\n" +
|
|
514
|
+
"# land without product feature work so reviewers treat the payload as machine-managed.\n" +
|
|
515
|
+
"# Delete this file if you do not want the guard.\n" +
|
|
515
516
|
"on:\n" +
|
|
516
517
|
" pull_request:\n\n" +
|
|
517
518
|
"permissions:\n" +
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent turn-shape hard-stop for text-repetition hang (FC14 / #3131).
|
|
3
|
+
*
|
|
4
|
+
* Soft skill prose (#2943) alone is insufficient: parents still burn the full
|
|
5
|
+
* output budget on identical progress sentences with zero tool_use after leaf
|
|
6
|
+
* announce. This pure library is the Directive-side machine-checkable gate.
|
|
7
|
+
*
|
|
8
|
+
* Hosts (OpenClaw, swarm parents, review monitors) SHOULD evaluate the parent
|
|
9
|
+
* turn stream mid-generation and hard-stop when `ok` is false.
|
|
10
|
+
*
|
|
11
|
+
* Illegal shape (MUST NOT):
|
|
12
|
+
* N > maxIdenticalWithoutTool (default 2) near-identical assistant sentences
|
|
13
|
+
* (or streaming text chunks) in one turn with no tool_use and no yield.
|
|
14
|
+
*
|
|
15
|
+
* Legal post-announce shapes:
|
|
16
|
+
* 1. Tool batch (ground-truth) then one consolidate
|
|
17
|
+
* 2. sessions_yield / wait without filler
|
|
18
|
+
* 3. One short user answer that is NOT a repeated progress line
|
|
19
|
+
*/
|
|
20
|
+
/** Failure class codes for operator / host recovery surfaces. */
|
|
21
|
+
export type ParentTurnFailClass = "none" | "FC14" | "text-repetition-hang" | "progress-only-no-tool";
|
|
22
|
+
/** Ordered events observed within a single parent turn. */
|
|
23
|
+
export type ParentTurnEvent = {
|
|
24
|
+
kind: "assistant_text";
|
|
25
|
+
text: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "tool_use";
|
|
28
|
+
name?: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "yield";
|
|
31
|
+
};
|
|
32
|
+
export interface ParentTurnShapeInput {
|
|
33
|
+
/** Ordered events from one parent turn (streaming deltas may be separate). */
|
|
34
|
+
readonly events: readonly ParentTurnEvent[];
|
|
35
|
+
/**
|
|
36
|
+
* When true, multi-sentence progress-only text with zero tools/yield is also
|
|
37
|
+
* illegal even without exact N>2 identity (post-`subagent_announce` policy).
|
|
38
|
+
*/
|
|
39
|
+
readonly afterSubagentAnnounce?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Max near-identical consecutive (or within-turn) text units allowed without
|
|
42
|
+
* tool_use / yield. Issue contract: MUST NOT emit N>2 → default max = 2.
|
|
43
|
+
*/
|
|
44
|
+
readonly maxIdenticalWithoutTool?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface ParentTurnShapeResult {
|
|
47
|
+
readonly ok: boolean;
|
|
48
|
+
/** Primary fail class (`FC14` aliases `text-repetition-hang`). */
|
|
49
|
+
readonly failClass: ParentTurnFailClass;
|
|
50
|
+
readonly reasons: readonly string[];
|
|
51
|
+
/** Highest count of near-identical text units observed without tool/yield. */
|
|
52
|
+
readonly maxIdenticalCount: number;
|
|
53
|
+
readonly hasToolUse: boolean;
|
|
54
|
+
readonly hasYield: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Canonical fail class for the hang (#3131 / soft FC14 recurrence of #2943). */
|
|
57
|
+
export declare const PARENT_TURN_FAIL_FC14: "FC14";
|
|
58
|
+
/** Default max identical text units without tool/yield (N>2 is illegal). */
|
|
59
|
+
export declare const DEFAULT_MAX_IDENTICAL_WITHOUT_TOOL = 2;
|
|
60
|
+
/**
|
|
61
|
+
* Normalize assistant text for identity comparison.
|
|
62
|
+
* Collapses whitespace, lowercases, strips common trailing punctuation.
|
|
63
|
+
*/
|
|
64
|
+
export declare function normalizeTurnText(text: string): string;
|
|
65
|
+
/** Split a blob into sentence-like units (periods, newlines, ellipsis). */
|
|
66
|
+
export declare function splitTextUnits(text: string): string[];
|
|
67
|
+
/**
|
|
68
|
+
* True when two normalized strings are near-identical (exact, containment with
|
|
69
|
+
* high length ratio, or high word Jaccard). Short units never match.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isNearIdentical(a: string, b: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Detect when a single blob itself embeds the same sentence many times
|
|
74
|
+
* (model streams one giant assistant message of repeated lines).
|
|
75
|
+
*/
|
|
76
|
+
export declare function countRepeatedUnitsInBlob(text: string): number;
|
|
77
|
+
/**
|
|
78
|
+
* Evaluate whether a parent turn shape is legal under the FC14 hard-stop.
|
|
79
|
+
* Pure / side-effect free — safe for mid-stream host gates and unit tests.
|
|
80
|
+
*/
|
|
81
|
+
export declare function evaluateParentTurnShape(input: ParentTurnShapeInput): ParentTurnShapeResult;
|
|
82
|
+
/** Convenience: true when the turn is an illegal text-repetition hang. */
|
|
83
|
+
export declare function isTextRepetitionHang(input: ParentTurnShapeInput): boolean;
|
|
84
|
+
//# sourceMappingURL=evaluate.d.ts.map
|