@lazyingart/agintiflow 0.20.289 → 0.20.291

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.
@@ -79,6 +79,14 @@ tools:
79
79
 
80
80
  Selected skills are injected into the plan and execution prompts. The LLM still decides what to do; skills only provide domain playbooks and guardrails.
81
81
 
82
+ Automatic selection requires substantive relevance. Exact IDs, focused short
83
+ requests, specific triggers, and several matching description terms score
84
+ strongly. Generic words such as `data`, `search`, `project`, or `commit` are
85
+ discounted when they occur incidentally inside a longer request, and automatic
86
+ mode omits weak matches instead of filling the skill budget. Explicit profiles
87
+ and direct topic wording remain authoritative. This keeps unrelated guidance
88
+ out of the context without hard-coding task-specific exclusions.
89
+
82
90
  ## Adding A Skill
83
91
 
84
92
  For a reusable project workflow, create `.aginti/skills/<id>/SKILL.md` with valid YAML frontmatter and a short Markdown body. These skills are loaded from the current project, shown as `source=project-local`, and are preferred for task-specific knowledge that should not become core runtime policy.
@@ -408,3 +408,26 @@ The same real session was replayed against the patched source at goal revision
408
408
  The target stayed at commit `2b35928` with no mutation. Focused planning,
409
409
  truthful-completion, and dynamic-budget regressions pass, as does the complete
410
410
  AgInTiFlow npm suite. The fix is released in AgInTiFlow `0.20.251`.
411
+
412
+ ### Exact source recovery after context loss
413
+
414
+ `database-migration-049` repeated an imperfect SQLite repair task against a
415
+ fresh broken workspace. Automatic routing selected only `qa-testing` and
416
+ `database`, but the first run read the canonical source immediately before
417
+ proactive context compaction. The compacted model correctly requested that
418
+ source again; the retained failed-test read budget exposed a different file,
419
+ so two safe but schema-invalid reads stopped the session without dispatch.
420
+
421
+ AgInTiFlow now treats proactive compaction, local context-window recovery,
422
+ model-timeout compaction, and same-task continuation as bounded context-loss
423
+ boundaries. A current failed-test packet may reopen only its evidence-derived
424
+ production repair paths after such a boundary. Tests, broad discovery,
425
+ arbitrary commands, and unrelated writes remain closed. Once the exact source
426
+ is read in the new context, the ordinary mutation and verification gates apply.
427
+
428
+ The original session resumed against the patched runtime, repaired the
429
+ transactional schema migration, preserved IDs and tags on URL updates, made
430
+ punctuation search literal, added regression coverage, passed four unit tests,
431
+ and committed a clean tree at `b956470`. The independent migration-safety
432
+ contract also passed. The complete npm suite passes, and the runtime fix is
433
+ released in AgInTiFlow `0.20.291`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.289",
3
+ "version": "0.20.291",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -2818,6 +2818,113 @@ assertStrict.deepEqual(
2818
2818
  ["service_ctl.py"],
2819
2819
  "DeepSeek repair reread fallback was not constrained to the canonical source"
2820
2820
  );
2821
+ const postCompactionRepairRuntime = nextStepRuntimeConfig(
2822
+ { provider: "deepseek", taskProfile: "qa" },
2823
+ {
2824
+ ...packetPathReadState,
2825
+ meta: {
2826
+ ...packetPathReadState.meta,
2827
+ failedTestRecoveryPacket: {
2828
+ ...packetPathReadState.meta.failedTestRecoveryPacket,
2829
+ paths: ["tests/test_service_ctl.py", "legacy_fixture.py", "service_ctl.py"],
2830
+ repairPaths: ["legacy_fixture.py", "service_ctl.py"],
2831
+ },
2832
+ toolLoop: {
2833
+ stagnationEpoch: 10,
2834
+ lastContextRecovery: {
2835
+ reason: "proactive-context-compaction",
2836
+ at: "2026-08-24T02:00:13.000Z",
2837
+ preservedStaticEvidence: true,
2838
+ },
2839
+ recent: [
2840
+ ...packetPathReadState.meta.toolLoop.recent,
2841
+ {
2842
+ toolName: "read_file",
2843
+ path: "tests/test_service_ctl.py",
2844
+ ok: true,
2845
+ blocked: false,
2846
+ at: "2026-08-24T02:00:12.000Z",
2847
+ },
2848
+ {
2849
+ toolName: "read_file",
2850
+ path: "legacy_fixture.py",
2851
+ ok: true,
2852
+ blocked: false,
2853
+ at: "2026-08-24T02:00:12.500Z",
2854
+ },
2855
+ ],
2856
+ },
2857
+ },
2858
+ }
2859
+ );
2860
+ assertStrict.deepEqual(
2861
+ postCompactionRepairRuntime.testFailureRepairContextPaths,
2862
+ ["legacy_fixture.py", "service_ctl.py"],
2863
+ "context compaction did not reopen the exact production source lost from model context"
2864
+ );
2865
+ assertStrict.equal(
2866
+ postCompactionRepairRuntime.testFailureRepairNeedsPatchContext,
2867
+ true,
2868
+ "context compaction did not restore one bounded source-context turn before mutation"
2869
+ );
2870
+ const postCompactionRepairTools = selectProgressiveTools(allTools, {
2871
+ config: postCompactionRepairRuntime,
2872
+ goal: "Continue the failed-test repair after proactive context compaction.",
2873
+ profile: "qa",
2874
+ });
2875
+ sameNames(
2876
+ postCompactionRepairTools,
2877
+ ["read_file", "apply_patch", "finish"],
2878
+ "post-compaction repair reopened broad discovery or verification tools"
2879
+ );
2880
+ assertStrict.deepEqual(
2881
+ postCompactionRepairTools[0].function.parameters.properties.path.enum,
2882
+ ["legacy_fixture.py", "service_ctl.py"],
2883
+ "post-compaction repair did not constrain rereads to all canonical production sources"
2884
+ );
2885
+ const sameTaskContinuationRepairRuntime = nextStepRuntimeConfig(
2886
+ { provider: "deepseek", taskProfile: "qa" },
2887
+ {
2888
+ ...packetPathReadState,
2889
+ meta: {
2890
+ ...packetPathReadState.meta,
2891
+ failedTestRecoveryPacket: {
2892
+ ...packetPathReadState.meta.failedTestRecoveryPacket,
2893
+ paths: ["tests/test_service_ctl.py", "legacy_fixture.py", "service_ctl.py"],
2894
+ repairPaths: ["legacy_fixture.py", "service_ctl.py"],
2895
+ },
2896
+ toolLoop: {
2897
+ stagnationEpoch: 11,
2898
+ lastContextRecovery: {
2899
+ reason: "same-task-continuation",
2900
+ at: "2026-08-24T02:01:00.000Z",
2901
+ },
2902
+ recent: [
2903
+ ...packetPathReadState.meta.toolLoop.recent,
2904
+ {
2905
+ toolName: "read_file",
2906
+ path: "tests/test_service_ctl.py",
2907
+ ok: true,
2908
+ blocked: false,
2909
+ at: "2026-08-24T02:00:12.000Z",
2910
+ },
2911
+ {
2912
+ toolName: "read_file",
2913
+ path: "legacy_fixture.py",
2914
+ ok: true,
2915
+ blocked: false,
2916
+ at: "2026-08-24T02:00:12.500Z",
2917
+ },
2918
+ ],
2919
+ },
2920
+ },
2921
+ }
2922
+ );
2923
+ assertStrict.deepEqual(
2924
+ sameTaskContinuationRepairRuntime.testFailureRepairContextPaths,
2925
+ ["legacy_fixture.py", "service_ctl.py"],
2926
+ "same-task continuation did not reopen all retained canonical production sources"
2927
+ );
2821
2928
  const testOnlyPacketState = {
2822
2929
  meta: {
2823
2930
  projectVerification: {
@@ -112,6 +112,24 @@ const pythonSkill = skills.find((skill) => skill.id === "python");
112
112
  assert(pythonSkill?.body.includes("PEP 701 relaxed f-strings"), "Python skill must mention 3.12 f-string compatibility traps");
113
113
  assert(pythonSkill?.body.includes("only proves the active interpreter"), "Python skill must guard syntax-check overclaims");
114
114
  assert(selectedIds("write SQL migrations for sqlite schema").includes("database"), "database prompt did not select database");
115
+ const imperfectDatabaseRepairSkills = selectedIds(
116
+ "The reading archive broke after its recent schema upgrade: an old library came back empty, and search gives surprising results for some punctuation. Please inspect the project, fix the underlying problems without discarding existing data, add useful regression coverage, run the checks, and commit the intentional repair. Keep the implementation small and standard-library only."
117
+ );
118
+ assert(imperfectDatabaseRepairSkills.includes("database"), "normal database repair prompt omitted database guidance");
119
+ assert(imperfectDatabaseRepairSkills.includes("qa-testing"), "normal database repair prompt omitted regression-test guidance");
120
+ for (const unrelated of [
121
+ "autonomous-artifact-pipeline",
122
+ "bgpt-paper-search",
123
+ "data-analysis",
124
+ "exa-search",
125
+ "exploratory-data-analysis",
126
+ "optimize-for-gpu",
127
+ ]) {
128
+ assert(
129
+ !imperfectDatabaseRepairSkills.includes(unrelated),
130
+ `normal database repair prompt selected unrelated ${unrelated} guidance`
131
+ );
132
+ }
115
133
  assert(selectedIds("debug Docker deployment logs and port config").includes("devops-deployment"), "devops prompt did not select devops-deployment");
116
134
  assert(selectedIds("review auth security and secrets handling").includes("security-review"), "security prompt did not select security-review");
117
135
  assert(selectedIds("make a PowerPoint pitch deck").includes("presentation-slides"), "slides prompt did not select presentation-slides");
@@ -14588,11 +14588,34 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
14588
14588
  Date.parse(String(state.meta?.failedTestRecoveryPacket?.generatedAt || "")),
14589
14589
  Date.parse(String(state.meta?.failedTestFocusedRecovery?.at || "")),
14590
14590
  ].filter(Number.isFinite);
14591
- const recoveryPacketContextAt = Number.isFinite(failureAt)
14591
+ const retainedRecoveryPacketContextAt = Number.isFinite(failureAt)
14592
14592
  ? failureAt
14593
14593
  : recoveryPacketContextMarkers.length
14594
14594
  ? Math.max(...recoveryPacketContextMarkers)
14595
14595
  : Number.NaN;
14596
+ const latestContextRecovery = state.meta?.toolLoop?.lastContextRecovery;
14597
+ const latestContextRecoveryAt = Date.parse(
14598
+ String(latestContextRecovery?.at || "")
14599
+ );
14600
+ const postContextLossRepairContext = Boolean(
14601
+ Number.isFinite(latestContextRecoveryAt) &&
14602
+ (!Number.isFinite(retainedRecoveryPacketContextAt) ||
14603
+ latestContextRecoveryAt > retainedRecoveryPacketContextAt) &&
14604
+ [
14605
+ "proactive-context-compaction",
14606
+ "local-context-budget-retry",
14607
+ "model-timeout-retry",
14608
+ "same-task-continuation",
14609
+ ].includes(String(latestContextRecovery?.reason || ""))
14610
+ );
14611
+ const recoveryPacketContextAt = postContextLossRepairContext
14612
+ ? latestContextRecoveryAt
14613
+ : retainedRecoveryPacketContextAt;
14614
+ const activeRecoveryPacketPaths = postContextLossRepairContext
14615
+ ? recoveryPacketRepairPaths
14616
+ .filter((item) => !failedTestPathIsTestEvidence(item))
14617
+ .slice(0, 6)
14618
+ : recoveryPacketPaths;
14596
14619
  const consumedRecoveryPacketPaths = new Set(
14597
14620
  Number.isFinite(recoveryPacketContextAt)
14598
14621
  ? (Array.isArray(toolLoop.recent) ? toolLoop.recent : [])
@@ -14610,7 +14633,7 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
14610
14633
  .filter(Boolean)
14611
14634
  : []
14612
14635
  );
14613
- const unreadRecoveryPacketPaths = recoveryPacketPaths.filter(
14636
+ const unreadRecoveryPacketPaths = activeRecoveryPacketPaths.filter(
14614
14637
  (item) => !consumedRecoveryPacketPaths.has(item)
14615
14638
  );
14616
14639
  const recoveryPacketReadCounts = new Map();
@@ -14635,7 +14658,7 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
14635
14658
  }
14636
14659
  }
14637
14660
  const exactRecoveryPacketContextActive = Boolean(
14638
- recoveryPacketPaths.length && Number.isFinite(recoveryPacketContextAt)
14661
+ activeRecoveryPacketPaths.length && Number.isFinite(recoveryPacketContextAt)
14639
14662
  );
14640
14663
  const recoveryPacketHasProductionContext = [
14641
14664
  ...recoveryPacketPaths,
@@ -419,21 +419,31 @@ const DESCRIPTION_STOP_WORDS = new Set([
419
419
  "after",
420
420
  "also",
421
421
  "before",
422
+ "checks",
422
423
  "content",
423
424
  "create",
425
+ "data",
424
426
  "exact",
427
+ "existing",
425
428
  "file",
426
429
  "files",
427
430
  "from",
428
431
  "general",
429
432
  "into",
433
+ "keep",
434
+ "only",
430
435
  "output",
431
436
  "path",
432
437
  "paths",
433
438
  "project",
434
439
  "read",
440
+ "reading",
441
+ "recent",
435
442
  "report",
436
443
  "request",
444
+ "results",
445
+ "search",
446
+ "standard",
437
447
  "task",
438
448
  "that",
439
449
  "their",
@@ -443,14 +453,19 @@ const DESCRIPTION_STOP_WORDS = new Set([
443
453
  "user",
444
454
  "when",
445
455
  "with",
456
+ "without",
446
457
  "work",
447
458
  "write",
448
459
  ]);
449
460
 
450
461
  const SKILL_ID_STOP_WORDS = new Set([
462
+ "and",
451
463
  "agent",
452
464
  "aginti",
465
+ "for",
453
466
  "development",
467
+ "of",
468
+ "the",
454
469
  "production",
455
470
  "skill",
456
471
  "system",
@@ -459,6 +474,30 @@ const SKILL_ID_STOP_WORDS = new Set([
459
474
  "workflow",
460
475
  ]);
461
476
 
477
+ // These words are useful in a focused request, but occur incidentally across
478
+ // many unrelated tasks. Weakening them in longer goals keeps one generic word
479
+ // from consuming a full skill slot while preserving short requests such as
480
+ // "analyze data" or "commit changes".
481
+ const GENERIC_ROUTING_TERMS = new Set([
482
+ "analysis",
483
+ "app",
484
+ "application",
485
+ "code",
486
+ "commit",
487
+ "data",
488
+ "file",
489
+ "files",
490
+ "project",
491
+ "report",
492
+ "result",
493
+ "results",
494
+ "search",
495
+ "test",
496
+ "tests",
497
+ "testing",
498
+ "work",
499
+ ]);
500
+
462
501
  function descriptionTerms(value = "") {
463
502
  return [
464
503
  ...new Set(
@@ -470,7 +509,16 @@ function descriptionTerms(value = "") {
470
509
  ];
471
510
  }
472
511
 
473
- function scoreSkill(skill, text, taskProfile) {
512
+ function focusedGenericSignal(goalText, needle) {
513
+ if (!GENERIC_ROUTING_TERMS.has(needle)) return true;
514
+ const terms = String(goalText || "")
515
+ .toLowerCase()
516
+ .split(/[^a-z0-9+#.]+/)
517
+ .filter(Boolean);
518
+ return terms.length <= 4 && textHasTrigger(goalText, needle);
519
+ }
520
+
521
+ function scoreSkill(skill, text, taskProfile, goalText = text) {
474
522
  let score = 0;
475
523
  if (skill.id === taskProfile) score += 10;
476
524
  if (skill.triggers.includes(taskProfile)) score += 6;
@@ -479,13 +527,14 @@ function scoreSkill(skill, text, taskProfile) {
479
527
  .split(/[^a-z0-9+#.]+/)
480
528
  .filter((term) => term.length >= 3 && !SKILL_ID_STOP_WORDS.has(term));
481
529
  for (const term of idTerms) {
482
- if (textHasTrigger(text, term)) score += 2;
530
+ if (textHasTrigger(text, term)) score += focusedGenericSignal(goalText, term) ? 2 : 0.5;
483
531
  }
484
532
  for (const trigger of skill.triggers) {
485
533
  const needle = trigger.toLowerCase();
486
534
  if (!needle) continue;
487
535
  if (textHasTrigger(text, needle)) {
488
- score += Math.max(2, Math.min(6, Math.ceil(needle.length / 6)));
536
+ const triggerScore = Math.max(2, Math.min(6, Math.ceil(needle.length / 6)));
537
+ score += focusedGenericSignal(goalText, needle) ? triggerScore : 0.5;
489
538
  continue;
490
539
  }
491
540
  const triggerTerms = descriptionTerms(needle);
@@ -495,7 +544,7 @@ function scoreSkill(skill, text, taskProfile) {
495
544
  }
496
545
  const descriptionMatches = descriptionTerms(skill.description).filter((token) => textHasTrigger(text, token));
497
546
  if (descriptionMatches.length >= 3) {
498
- score += Math.min(3, descriptionMatches.length * 0.35);
547
+ score += Math.min(3, descriptionMatches.length * 0.5);
499
548
  }
500
549
  return score;
501
550
  }
@@ -516,19 +565,19 @@ function textHasTrigger(text, needle) {
516
565
 
517
566
  export function selectSkillsForGoal(goal = "", { taskProfile = "auto", limit = 6, includeBody = true, projectRoot = process.cwd() } = {}) {
518
567
  const goalText = String(goal || "").toLowerCase();
519
- const text = `${goalText} ${taskProfile}`.toLowerCase();
568
+ const text = goalText;
520
569
  const skills = listSkills({ includeBody, projectRoot });
521
570
  const ranked = skills
522
571
  .map((skill) => ({
523
572
  skill,
524
- score: scoreSkill(skill, text, taskProfile),
525
- directGoalScore: scoreSkill(skill, goalText, "auto"),
573
+ score: scoreSkill(skill, text, taskProfile, goalText),
574
+ directGoalScore: scoreSkill(skill, goalText, "auto", goalText),
526
575
  }))
527
576
  .filter((item) => item.score > 0)
528
577
  .sort((a, b) => b.score - a.score || a.skill.id.localeCompare(b.skill.id));
529
578
  const explicitProfile = Boolean(taskProfile && taskProfile !== "auto");
530
579
  const bestScore = ranked[0]?.score || 0;
531
- const relevanceFloor = explicitProfile && bestScore >= 6 ? Math.max(2, bestScore * 0.5) : 0;
580
+ const relevanceFloor = explicitProfile && bestScore >= 6 ? Math.max(2, bestScore * 0.5) : 2;
532
581
  const scored = ranked
533
582
  .filter((item) => item.score >= relevanceFloor || item.directGoalScore >= 4)
534
583
  .map((item) => item.skill);