@event4u/agent-config 9.16.0 → 9.18.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/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +80 -130
- package/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +100 -48
- package/dist/agent-src/commands/challenge-me/vision.md +5 -0
- package/dist/agent-src/commands/commit.md +1 -1
- package/dist/agent-src/commands/condense.md +19 -64
- package/dist/agent-src/commands/evals/agent-handoff.json +13 -1
- package/dist/agent-src/commands/evals/rule-compliance-audit.json +46 -0
- package/dist/agent-src/commands/feature/plan.md +20 -0
- package/dist/agent-src/commands/feature/roadmap.md +19 -0
- package/dist/agent-src/commands/memory/promote.md +14 -1
- package/dist/agent-src/commands/mode.md +9 -5
- package/dist/agent-src/commands/optimize/deep.md +72 -11
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/roadmap/create.md +29 -0
- package/dist/agent-src/commands/rule-compliance-audit.md +28 -5
- package/dist/agent-src/contexts/authority/commit-mechanics.md +5 -5
- package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
- package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +4 -1
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +27 -1
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +133 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +5 -2
- package/dist/agent-src/profiles/agency.yml +3 -3
- package/dist/agent-src/profiles/content_creator.yml +2 -2
- package/dist/agent-src/profiles/developer.yml +2 -2
- package/dist/agent-src/profiles/finance.yml +1 -1
- package/dist/agent-src/profiles/founder.yml +2 -2
- package/dist/agent-src/profiles/ops.yml +2 -2
- package/dist/agent-src/rules/active-remediation.md +2 -0
- package/dist/agent-src/rules/architecture.md +4 -2
- package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/brand-consistency.md +10 -62
- package/dist/agent-src/rules/brand-source-of-truth.md +62 -11
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/cli-output-handling.md +5 -0
- package/dist/agent-src/rules/code-comment-discipline.md +3 -0
- package/dist/agent-src/rules/commit-conventions.md +2 -0
- package/dist/agent-src/rules/context-hygiene.md +3 -2
- package/dist/agent-src/rules/cross-source-consistency.md +3 -0
- package/dist/agent-src/rules/design-fidelity.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
- package/dist/agent-src/rules/docker-commands.md +4 -0
- package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +5 -2
- package/dist/agent-src/rules/domain-safety-pii.md +1 -1
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +9 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +4 -0
- package/dist/agent-src/rules/improve-before-implement.md +4 -0
- package/dist/agent-src/rules/laravel-routing.md +4 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
- package/dist/agent-src/rules/media-governance-routing.md +5 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/no-roadmap-references.md +2 -0
- package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
- package/dist/agent-src/rules/output-discipline.md +9 -2
- package/dist/agent-src/rules/php-coding.md +2 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +3 -0
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +5 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +2 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/rule-type-governance.md +2 -0
- package/dist/agent-src/rules/scale-discipline.md +3 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +9 -2
- package/dist/agent-src/rules/senior-engineering-discipline.md +6 -0
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +4 -0
- package/dist/agent-src/rules/source-discovery-gate.md +3 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +2 -0
- package/dist/agent-src/rules/think-before-action.md +5 -0
- package/dist/agent-src/rules/token-budget-discipline.md +2 -2
- package/dist/agent-src/rules/ui-audit-gate.md +2 -0
- package/dist/agent-src/rules/user-interaction.md +3 -4
- package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +4 -4
- package/dist/agent-src/skills/copilot-config/SKILL.md +1 -1
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +7 -4
- package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/SKILL.md +6 -180
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +100 -0
- package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +91 -0
- package/dist/agent-src/skills/design-review/SKILL.md +4 -141
- package/dist/agent-src/skills/design-review/references/review-communication.md +92 -0
- package/dist/agent-src/skills/design-review/references/verification-automation.md +61 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -27
- package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +31 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +4 -289
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +227 -0
- package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +75 -0
- package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/readme-reviewer/SKILL.md +2 -1
- package/dist/agent-src/skills/roadmap-management/SKILL.md +10 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +12 -1
- package/dist/agent-src/skills/rule-writing/SKILL.md +4 -2
- package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +3 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +2 -2
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +6 -3
- package/dist/agent-src/templates/agent-settings.md +13 -15
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +6 -2
- package/dist/agent-src/templates/persona.md +2 -2
- package/dist/agent-src/templates/roadmaps.md +13 -1
- package/dist/agent-src/templates/rule.md +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
- package/dist/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
- package/dist/agent-src/templates/skill.md +1 -1
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-AIIUU73N.js +2040 -0
- package/dist/cli-delegate/{chunk-ZXXJLL5K.js → chunk-BSLUHV2F.js} +127 -1487
- package/dist/cli-delegate/{chunk-H574KOUH.js → chunk-ELMFOQPW.js} +35 -1
- package/dist/cli-delegate/chunk-LF2KKJ4Q.js +183 -0
- package/dist/cli-delegate/chunk-VSYT7OHO.js +1382 -0
- package/dist/cli-delegate/chunk-XGA2NTU4.js +3727 -0
- package/dist/cli-delegate/cmd_conformance.js +4 -3
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +3 -2
- package/dist/cli-delegate/cmd_explain.js +36 -36
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +1169 -0
- package/dist/cli-delegate/cmd_memory_get.js +35 -3754
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +249 -0
- package/dist/cli-delegate/cmd_route_explain.js +285 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +3 -3
- package/dist/cli-delegate/cmd_upgrade.js +3 -3
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +84 -84
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +602 -507
- package/dist/install/install.mjs +34 -0
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +25 -3
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_src.js +39 -6
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/server/schemas/settings.js +25 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/guidelines/abstraction-thresholds.md +53 -0
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -1
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +7 -0
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +2 -2
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +5 -3
- package/docs/guidelines/agent-infra/size-and-scope.md +8 -2
- package/docs/guidelines/component-oriented-and-oop-development.md +11 -6
- package/docs/guidelines/docs/readme-size-and-splitting.md +5 -2
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
- package/src/config/agent-settings.template.yml +68 -0
- package/src/config/evaluator-budgets.json +8 -11
- package/src/config/gate-coverage.yml +70 -0
- package/src/scripts/_cli/cmd_explain.ts +41 -36
- package/src/scripts/_cli/cmd_handoff.ts +405 -0
- package/src/scripts/_cli/cmd_route_audit.ts +290 -0
- package/src/scripts/_cli/cmd_route_explain.ts +269 -0
- package/src/scripts/_cli/handoff_generate.ts +453 -0
- package/src/scripts/_cli/handoff_sessions.ts +435 -0
- package/src/scripts/_dispatch.bash +32 -0
- package/src/scripts/_lib/agent_src.ts +36 -6
- package/src/scripts/_lib/git_env.ts +40 -0
- package/src/scripts/_lib/md_table.ts +27 -0
- package/src/scripts/_lib/planning_settings.ts +43 -0
- package/src/scripts/_lib/release_material.ts +156 -0
- package/src/scripts/_lib/router_match.ts +301 -0
- package/src/scripts/annotate_r1_outcomes.ts +258 -0
- package/src/scripts/check_completion_review.ts +1140 -0
- package/src/scripts/check_condensation.ts +2 -1
- package/src/scripts/check_finding_dispositions.ts +278 -0
- package/src/scripts/check_pack_size.ts +4 -3
- package/src/scripts/check_references.ts +1 -0
- package/src/scripts/check_release_highlights.ts +300 -0
- package/src/scripts/check_release_pr_shape.ts +4 -0
- package/src/scripts/check_release_surface_equality.ts +132 -0
- package/src/scripts/check_reply_consistency.ts +187 -138
- package/src/scripts/compile_router.ts +20 -3
- package/src/scripts/dispatch_r2_reviewer.ts +968 -0
- package/src/scripts/eval_ui_triviality.ts +156 -0
- package/src/scripts/handoff_context_hook.ts +148 -0
- package/src/scripts/hook_manifest.yaml +17 -7
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/lint_abstraction_thresholds.ts +327 -0
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_plan_risk_register.ts +931 -0
- package/src/scripts/lint_profile_personas.ts +153 -0
- package/src/scripts/lint_token_budget_discipline.ts +148 -0
- package/src/scripts/lint_trigger_collisions.ts +257 -0
- package/src/scripts/release.ts +156 -32
- package/src/scripts/release_drill.ts +491 -0
- package/src/scripts/router_telemetry.ts +16 -290
- package/src/scripts/schemas/command.schema.json +33 -0
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/scripts/self_review_gate.ts +41 -3
- package/src/scripts/skill_linter.ts +3 -1
- package/src/scripts/smoke/router.sh +1 -1
- package/src/scripts/update_counts.ts +60 -12
- package/src/server/schemas/settings.ts +41 -0
- package/dist/cli-delegate/{chunk-XX6TOPD7.js → chunk-WR6GTRHJ.js} +3 -3
|
@@ -1,3737 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
} from "./chunk-J55WI3RR.js";
|
|
9
|
-
|
|
10
|
-
// src/scripts/_cli/cmd_memory_get.ts
|
|
11
|
-
import process6 from "node:process";
|
|
12
|
-
import * as fs8 from "node:fs";
|
|
13
|
-
import * as path8 from "node:path";
|
|
14
|
-
import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
15
|
-
|
|
16
|
-
// src/scripts/memory_lookup.ts
|
|
17
|
-
var import_yaml2 = __toESM(require_dist(), 1);
|
|
18
|
-
import * as fs7 from "node:fs";
|
|
19
|
-
import * as path7 from "node:path";
|
|
20
|
-
import process5 from "node:process";
|
|
21
|
-
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
22
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
23
|
-
|
|
24
|
-
// src/scripts/lint_hidden_unicode.ts
|
|
25
|
-
import * as fs2 from "node:fs";
|
|
26
|
-
import * as path2 from "node:path";
|
|
27
|
-
import { spawnSync } from "node:child_process";
|
|
28
|
-
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
29
|
-
|
|
30
|
-
// src/scripts/_lib/security_lint.ts
|
|
31
|
-
import * as fs from "node:fs";
|
|
32
|
-
import * as path from "node:path";
|
|
33
|
-
import { fileURLToPath } from "node:url";
|
|
34
|
-
var _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
35
|
-
var ROOT = path.resolve(_HERE, "..", "..", "..");
|
|
36
|
-
var EXAMPLE_FENCE_LANG = "security-example";
|
|
37
|
-
var GUIDELINE = "docs/guidelines/agent-infra/security-lint-containment.md";
|
|
38
|
-
var GUIDELINE_EPILOG = `False-positive containment (fenced security-example block, confidence weighting, per-file \`security-lint: allow\` pragma \u2014 no global allowlist): see ${GUIDELINE}.`;
|
|
39
|
-
var DEFAULT_SCAN_ROOTS = [
|
|
40
|
-
"src/skills",
|
|
41
|
-
"src/rules",
|
|
42
|
-
"src/agent-src",
|
|
43
|
-
"src/domains",
|
|
44
|
-
"dist/agent-src"
|
|
45
|
-
];
|
|
46
|
-
var _EXAMPLE_PATH = /(^|\/)(docs|evals|tests?|fixtures?)(\/|$)|example|template|sample|\/_template/i;
|
|
47
|
-
var _PRAGMA = /<!--\s*security-lint:\s*allow\s+([A-Za-z0-9_-]+)\s+"([^"]+)"\s*-->/g;
|
|
48
|
-
var _FENCE = /^(\s*)(`{3,}|~{3,})\s*([A-Za-z0-9_-]*)\s*$/;
|
|
49
|
-
var SEVERITY_RANK = { LOW: 1, MED: 2, HIGH: 3 };
|
|
50
|
-
var Finding = class {
|
|
51
|
-
path;
|
|
52
|
-
// repo-relative
|
|
53
|
-
line;
|
|
54
|
-
// 1-based; 0 = file-level
|
|
55
|
-
check;
|
|
56
|
-
// stable check id, also the pragma key
|
|
57
|
-
severity;
|
|
58
|
-
// HIGH | MED | LOW
|
|
59
|
-
message;
|
|
60
|
-
weight;
|
|
61
|
-
// 1.0 or 0.25
|
|
62
|
-
constructor(pathArg, line, check, severity, message, weight = 1) {
|
|
63
|
-
this.path = pathArg;
|
|
64
|
-
this.line = line;
|
|
65
|
-
this.check = check;
|
|
66
|
-
this.severity = severity;
|
|
67
|
-
this.message = message;
|
|
68
|
-
this.weight = weight;
|
|
69
|
-
}
|
|
70
|
-
/** A HIGH-severity, full-weight finding fails the build. */
|
|
71
|
-
get is_fail() {
|
|
72
|
-
return this.severity === "HIGH" && this.weight >= 1;
|
|
73
|
-
}
|
|
74
|
-
/** Mirror Python dataclass `__dict__` key order for `--json` parity. */
|
|
75
|
-
toDict() {
|
|
76
|
-
return {
|
|
77
|
-
path: this.path,
|
|
78
|
-
line: this.line,
|
|
79
|
-
check: this.check,
|
|
80
|
-
severity: this.severity,
|
|
81
|
-
message: this.message,
|
|
82
|
-
// float in Python → render integer-valued (1.0) as `1.0`.
|
|
83
|
-
weight: new PyFloat(this.weight)
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
function is_example_path(rel_path) {
|
|
88
|
-
return _EXAMPLE_PATH.test(rel_path);
|
|
89
|
-
}
|
|
90
|
-
function path_weight(rel_path) {
|
|
91
|
-
return is_example_path(rel_path) ? 0.25 : 1;
|
|
92
|
-
}
|
|
93
|
-
var ScannedFile = class {
|
|
94
|
-
path;
|
|
95
|
-
// absolute or as-given path
|
|
96
|
-
rel;
|
|
97
|
-
lines;
|
|
98
|
-
// per-line flags (1-based index → flag); index 0 unused
|
|
99
|
-
in_example_fence;
|
|
100
|
-
in_any_fence;
|
|
101
|
-
pragmas;
|
|
102
|
-
// check id → reason
|
|
103
|
-
weight;
|
|
104
|
-
constructor(pathArg, rel, lines, in_example_fence, in_any_fence, pragmas, weight) {
|
|
105
|
-
this.path = pathArg;
|
|
106
|
-
this.rel = rel;
|
|
107
|
-
this.lines = lines;
|
|
108
|
-
this.in_example_fence = in_example_fence;
|
|
109
|
-
this.in_any_fence = in_any_fence;
|
|
110
|
-
this.pragmas = pragmas;
|
|
111
|
-
this.weight = weight;
|
|
112
|
-
}
|
|
113
|
-
pragma_allows(check) {
|
|
114
|
-
return Object.prototype.hasOwnProperty.call(this.pragmas, check);
|
|
115
|
-
}
|
|
116
|
-
/** Yield [lineno, text] honouring the fence masks (mirrors iter_lines). */
|
|
117
|
-
*iter_lines(opts = {}) {
|
|
118
|
-
const skip_example_fence = opts.skip_example_fence ?? true;
|
|
119
|
-
const skip_any_fence = opts.skip_any_fence ?? false;
|
|
120
|
-
for (let i = 1; i <= this.lines.length; i++) {
|
|
121
|
-
if (skip_example_fence && this.in_example_fence[i]) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (skip_any_fence && this.in_any_fence[i]) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
yield [i, this.lines[i - 1]];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
function _splitlines(text) {
|
|
132
|
-
const out = [];
|
|
133
|
-
let buf = "";
|
|
134
|
-
const n = text.length;
|
|
135
|
-
let i = 0;
|
|
136
|
-
while (i < n) {
|
|
137
|
-
const ch = text[i];
|
|
138
|
-
const code = text.charCodeAt(i);
|
|
139
|
-
if (code === 13) {
|
|
140
|
-
out.push(buf);
|
|
141
|
-
buf = "";
|
|
142
|
-
if (i + 1 < n && text.charCodeAt(i + 1) === 10) {
|
|
143
|
-
i += 2;
|
|
144
|
-
} else {
|
|
145
|
-
i += 1;
|
|
146
|
-
}
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (code === 10 || code === 11 || code === 12 || code === 28 || code === 29 || code === 30 || code === 133 || code === 8232 || code === 8233) {
|
|
150
|
-
out.push(buf);
|
|
151
|
-
buf = "";
|
|
152
|
-
i += 1;
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
buf += ch;
|
|
156
|
-
i += 1;
|
|
157
|
-
}
|
|
158
|
-
if (buf !== "") {
|
|
159
|
-
out.push(buf);
|
|
160
|
-
}
|
|
161
|
-
return out;
|
|
162
|
-
}
|
|
163
|
-
function scan_file(filePath) {
|
|
164
|
-
let rel;
|
|
165
|
-
if (path.isAbsolute(filePath)) {
|
|
166
|
-
const root = path.resolve(ROOT);
|
|
167
|
-
const resolved = path.resolve(filePath);
|
|
168
|
-
if (resolved === root || resolved.startsWith(root + path.sep)) {
|
|
169
|
-
rel = path.relative(root, resolved).split(path.sep).join("/");
|
|
170
|
-
} else {
|
|
171
|
-
rel = path.basename(filePath);
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
rel = filePath.split(path.sep).join("/");
|
|
175
|
-
}
|
|
176
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
177
|
-
const lines = _splitlines(raw);
|
|
178
|
-
const n = lines.length;
|
|
179
|
-
const in_example = new Array(n + 1).fill(false);
|
|
180
|
-
const in_any = new Array(n + 1).fill(false);
|
|
181
|
-
let fence_open = false;
|
|
182
|
-
let fence_marker = "";
|
|
183
|
-
let fence_is_example = false;
|
|
184
|
-
for (let idx = 1; idx <= n; idx++) {
|
|
185
|
-
const text = lines[idx - 1];
|
|
186
|
-
const m = _FENCE.exec(text);
|
|
187
|
-
if (m && !fence_open) {
|
|
188
|
-
fence_open = true;
|
|
189
|
-
fence_marker = m[2][0];
|
|
190
|
-
fence_is_example = m[3] === EXAMPLE_FENCE_LANG;
|
|
191
|
-
in_any[idx] = true;
|
|
192
|
-
in_example[idx] = fence_is_example;
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
if (fence_open) {
|
|
196
|
-
in_any[idx] = true;
|
|
197
|
-
in_example[idx] = fence_is_example;
|
|
198
|
-
const cm = _FENCE.exec(text);
|
|
199
|
-
if (cm && cm[2][0] === fence_marker && cm[3] === "") {
|
|
200
|
-
fence_open = false;
|
|
201
|
-
fence_is_example = false;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
const pragmas = {};
|
|
206
|
-
for (const text of lines) {
|
|
207
|
-
_PRAGMA.lastIndex = 0;
|
|
208
|
-
let m;
|
|
209
|
-
while ((m = _PRAGMA.exec(text)) !== null) {
|
|
210
|
-
pragmas[m[1]] = m[2];
|
|
211
|
-
if (m.index === _PRAGMA.lastIndex) {
|
|
212
|
-
_PRAGMA.lastIndex += 1;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return new ScannedFile(
|
|
217
|
-
filePath,
|
|
218
|
-
rel,
|
|
219
|
-
lines,
|
|
220
|
-
in_example,
|
|
221
|
-
in_any,
|
|
222
|
-
pragmas,
|
|
223
|
-
path_weight(rel)
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
function _rglobSorted(base) {
|
|
227
|
-
const found = [];
|
|
228
|
-
const walk = (dir) => {
|
|
229
|
-
let entries;
|
|
230
|
-
try {
|
|
231
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
232
|
-
} catch {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
for (const e of entries) {
|
|
236
|
-
const full = path.join(dir, e.name);
|
|
237
|
-
found.push(full);
|
|
238
|
-
if (e.isDirectory()) {
|
|
239
|
-
walk(full);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
walk(base);
|
|
244
|
-
found.sort((a, b) => _pathCmp(a, b));
|
|
245
|
-
return found;
|
|
246
|
-
}
|
|
247
|
-
function _pathCmp(a, b) {
|
|
248
|
-
const pa = a.split(path.sep);
|
|
249
|
-
const pb = b.split(path.sep);
|
|
250
|
-
const len = Math.min(pa.length, pb.length);
|
|
251
|
-
for (let i = 0; i < len; i++) {
|
|
252
|
-
if (pa[i] < pb[i]) return -1;
|
|
253
|
-
if (pa[i] > pb[i]) return 1;
|
|
254
|
-
}
|
|
255
|
-
if (pa.length < pb.length) return -1;
|
|
256
|
-
if (pa.length > pb.length) return 1;
|
|
257
|
-
return 0;
|
|
258
|
-
}
|
|
259
|
-
function* iter_corpus(roots = DEFAULT_SCAN_ROOTS, exts = [".md"]) {
|
|
260
|
-
for (const root of roots) {
|
|
261
|
-
const base = path.join(ROOT, root);
|
|
262
|
-
if (!fs.existsSync(base)) {
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
for (const p of _rglobSorted(base)) {
|
|
266
|
-
let st;
|
|
267
|
-
try {
|
|
268
|
-
st = fs.statSync(p);
|
|
269
|
-
} catch {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (st.isFile() && exts.includes(path.extname(p))) {
|
|
273
|
-
yield scan_file(p);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
function report(findings, opts) {
|
|
279
|
-
const check_label = opts.check_label;
|
|
280
|
-
if (findings.length === 0) {
|
|
281
|
-
process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note()}).
|
|
282
|
-
`);
|
|
283
|
-
return 0;
|
|
284
|
-
}
|
|
285
|
-
const fails = findings.filter((f) => f.is_fail);
|
|
286
|
-
const warns = findings.filter((f) => !f.is_fail);
|
|
287
|
-
const sorted = [...findings].sort((a, b) => {
|
|
288
|
-
const ra = -(SEVERITY_RANK[a.severity] ?? 0);
|
|
289
|
-
const rb = -(SEVERITY_RANK[b.severity] ?? 0);
|
|
290
|
-
if (ra !== rb) return ra - rb;
|
|
291
|
-
if (a.path < b.path) return -1;
|
|
292
|
-
if (a.path > b.path) return 1;
|
|
293
|
-
return a.line - b.line;
|
|
294
|
-
});
|
|
295
|
-
for (const f of sorted) {
|
|
296
|
-
const glyph = f.is_fail ? "\u{1F534}" : "\u26A0\uFE0F";
|
|
297
|
-
const loc = f.line ? `${f.path}:${f.line}` : f.path;
|
|
298
|
-
const wnote = f.weight >= 1 ? "" : ` (weight ${_pyG(f.weight)})`;
|
|
299
|
-
process.stdout.write(` ${glyph} [${f.severity}] ${f.check} \u2014 ${loc}${wnote}: ${f.message}
|
|
300
|
-
`);
|
|
301
|
-
}
|
|
302
|
-
process.stdout.write("\n");
|
|
303
|
-
if (fails.length > 0) {
|
|
304
|
-
process.stdout.write(
|
|
305
|
-
`\u274C ${check_label}: ${fails.length} blocking finding(s), ${warns.length} warning(s). Fix, or mark a true teaching example with a \`\`\`${EXAMPLE_FENCE_LANG} fence or a \`security-lint: allow\` pragma.
|
|
306
|
-
`
|
|
307
|
-
);
|
|
308
|
-
return 1;
|
|
309
|
-
}
|
|
310
|
-
process.stdout.write(`\u26A0\uFE0F ${check_label}: ${warns.length} warning(s), 0 blocking.
|
|
311
|
-
`);
|
|
312
|
-
return 0;
|
|
313
|
-
}
|
|
314
|
-
function _pyG(n) {
|
|
315
|
-
let s = n.toPrecision(6);
|
|
316
|
-
if (s.includes(".")) {
|
|
317
|
-
s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
318
|
-
}
|
|
319
|
-
return s;
|
|
320
|
-
}
|
|
321
|
-
function _corpus_note() {
|
|
322
|
-
return "scanned " + DEFAULT_SCAN_ROOTS.join(", ");
|
|
323
|
-
}
|
|
324
|
-
var PyFloat = class {
|
|
325
|
-
constructor(value) {
|
|
326
|
-
this.value = value;
|
|
327
|
-
}
|
|
328
|
-
value;
|
|
329
|
-
};
|
|
330
|
-
function _isPlainObject(v) {
|
|
331
|
-
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof PyFloat);
|
|
332
|
-
}
|
|
333
|
-
function _pyJsonStr(s) {
|
|
334
|
-
let out = '"';
|
|
335
|
-
for (const ch of s) {
|
|
336
|
-
const code = ch.codePointAt(0);
|
|
337
|
-
switch (ch) {
|
|
338
|
-
case '"':
|
|
339
|
-
out += '\\"';
|
|
340
|
-
break;
|
|
341
|
-
case "\\":
|
|
342
|
-
out += "\\\\";
|
|
343
|
-
break;
|
|
344
|
-
case "\n":
|
|
345
|
-
out += "\\n";
|
|
346
|
-
break;
|
|
347
|
-
case "\r":
|
|
348
|
-
out += "\\r";
|
|
349
|
-
break;
|
|
350
|
-
case " ":
|
|
351
|
-
out += "\\t";
|
|
352
|
-
break;
|
|
353
|
-
case "\b":
|
|
354
|
-
out += "\\b";
|
|
355
|
-
break;
|
|
356
|
-
case "\f":
|
|
357
|
-
out += "\\f";
|
|
358
|
-
break;
|
|
359
|
-
default:
|
|
360
|
-
if (code < 32) {
|
|
361
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
362
|
-
} else {
|
|
363
|
-
out += ch;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return out + '"';
|
|
368
|
-
}
|
|
369
|
-
function _pyJsonFloat(n) {
|
|
370
|
-
if (!Number.isFinite(n)) {
|
|
371
|
-
if (Number.isNaN(n)) return "NaN";
|
|
372
|
-
return n > 0 ? "Infinity" : "-Infinity";
|
|
373
|
-
}
|
|
374
|
-
return Number.isInteger(n) ? `${n}.0` : String(n);
|
|
375
|
-
}
|
|
376
|
-
function _pyJsonScalar(value) {
|
|
377
|
-
if (value === null || value === void 0) return "null";
|
|
378
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
379
|
-
if (value instanceof PyFloat) return _pyJsonFloat(value.value);
|
|
380
|
-
if (typeof value === "number") {
|
|
381
|
-
if (!Number.isFinite(value)) {
|
|
382
|
-
if (Number.isNaN(value)) return "NaN";
|
|
383
|
-
return value > 0 ? "Infinity" : "-Infinity";
|
|
384
|
-
}
|
|
385
|
-
return String(value);
|
|
386
|
-
}
|
|
387
|
-
if (typeof value === "string") return _pyJsonStr(value);
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
function py_json_dumps_indent2(value, depth = 0) {
|
|
391
|
-
return _dumpIndent2(value, depth, true);
|
|
392
|
-
}
|
|
393
|
-
function _dumpIndent2(value, depth, ensureAscii) {
|
|
394
|
-
const scalar = ensureAscii ? _pyJsonScalarAscii(value) : _pyJsonScalar(value);
|
|
395
|
-
if (scalar !== null) {
|
|
396
|
-
return scalar;
|
|
397
|
-
}
|
|
398
|
-
const pad = " ".repeat(2 * (depth + 1));
|
|
399
|
-
const closePad = " ".repeat(2 * depth);
|
|
400
|
-
if (Array.isArray(value)) {
|
|
401
|
-
if (value.length === 0) return "[]";
|
|
402
|
-
const items = value.map((v) => pad + _dumpIndent2(v, depth + 1, ensureAscii));
|
|
403
|
-
return `[
|
|
404
|
-
${items.join(",\n")}
|
|
405
|
-
${closePad}]`;
|
|
406
|
-
}
|
|
407
|
-
if (_isPlainObject(value)) {
|
|
408
|
-
const keys = Object.keys(value);
|
|
409
|
-
if (keys.length === 0) return "{}";
|
|
410
|
-
const items = keys.map(
|
|
411
|
-
(k) => `${pad}${ensureAscii ? _pyJsonStrAscii(k) : _pyJsonStr(k)}: ${_dumpIndent2(value[k], depth + 1, ensureAscii)}`
|
|
412
|
-
);
|
|
413
|
-
return `{
|
|
414
|
-
${items.join(",\n")}
|
|
415
|
-
${closePad}}`;
|
|
416
|
-
}
|
|
417
|
-
return ensureAscii ? _pyJsonStrAscii(String(value)) : _pyJsonStr(String(value));
|
|
418
|
-
}
|
|
419
|
-
function _pyJsonStrAscii(s) {
|
|
420
|
-
let out = '"';
|
|
421
|
-
for (const ch of s) {
|
|
422
|
-
const code = ch.codePointAt(0);
|
|
423
|
-
switch (ch) {
|
|
424
|
-
case '"':
|
|
425
|
-
out += '\\"';
|
|
426
|
-
break;
|
|
427
|
-
case "\\":
|
|
428
|
-
out += "\\\\";
|
|
429
|
-
break;
|
|
430
|
-
case "\n":
|
|
431
|
-
out += "\\n";
|
|
432
|
-
break;
|
|
433
|
-
case "\r":
|
|
434
|
-
out += "\\r";
|
|
435
|
-
break;
|
|
436
|
-
case " ":
|
|
437
|
-
out += "\\t";
|
|
438
|
-
break;
|
|
439
|
-
case "\b":
|
|
440
|
-
out += "\\b";
|
|
441
|
-
break;
|
|
442
|
-
case "\f":
|
|
443
|
-
out += "\\f";
|
|
444
|
-
break;
|
|
445
|
-
default:
|
|
446
|
-
if (code < 32) {
|
|
447
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
448
|
-
} else if (code < 127) {
|
|
449
|
-
out += ch;
|
|
450
|
-
} else if (code <= 65535) {
|
|
451
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
452
|
-
} else {
|
|
453
|
-
const cp = code - 65536;
|
|
454
|
-
const hi = 55296 + (cp >> 10);
|
|
455
|
-
const lo = 56320 + (cp & 1023);
|
|
456
|
-
out += `\\u${hi.toString(16).padStart(4, "0")}\\u${lo.toString(16).padStart(4, "0")}`;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return out + '"';
|
|
461
|
-
}
|
|
462
|
-
function _pyJsonScalarAscii(value) {
|
|
463
|
-
if (typeof value === "string") return _pyJsonStrAscii(value);
|
|
464
|
-
return _pyJsonScalar(value);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// src/scripts/lint_hidden_unicode.ts
|
|
468
|
-
var CHECK = "hidden-unicode";
|
|
469
|
-
var _BIDI = /* @__PURE__ */ new Set([
|
|
470
|
-
8234,
|
|
471
|
-
8235,
|
|
472
|
-
8236,
|
|
473
|
-
8237,
|
|
474
|
-
8238,
|
|
475
|
-
8294,
|
|
476
|
-
8295,
|
|
477
|
-
8296,
|
|
478
|
-
8297,
|
|
479
|
-
8206,
|
|
480
|
-
8207,
|
|
481
|
-
1564
|
|
482
|
-
]);
|
|
483
|
-
var _ZERO_WIDTH = /* @__PURE__ */ new Set([8203, 8204, 8205, 8288, 65279, 173]);
|
|
484
|
-
var _DEPRECATED = /* @__PURE__ */ new Set([
|
|
485
|
-
8298,
|
|
486
|
-
8299,
|
|
487
|
-
8300,
|
|
488
|
-
8301,
|
|
489
|
-
8302,
|
|
490
|
-
8303,
|
|
491
|
-
65529,
|
|
492
|
-
65530,
|
|
493
|
-
65531
|
|
494
|
-
]);
|
|
495
|
-
function _classify(cp) {
|
|
496
|
-
if (_BIDI.has(cp)) {
|
|
497
|
-
return "bidi-control";
|
|
498
|
-
}
|
|
499
|
-
if (_ZERO_WIDTH.has(cp)) {
|
|
500
|
-
return "zero-width";
|
|
501
|
-
}
|
|
502
|
-
if (cp >= 917504 && cp <= 917631) {
|
|
503
|
-
return "unicode-tag";
|
|
504
|
-
}
|
|
505
|
-
if (_DEPRECATED.has(cp)) {
|
|
506
|
-
return "deprecated-format";
|
|
507
|
-
}
|
|
508
|
-
if (cp >= 57344 && cp <= 63743 || cp >= 983040 && cp <= 1048573 || cp >= 1048576 && cp <= 1114109) {
|
|
509
|
-
return "private-use-area";
|
|
510
|
-
}
|
|
511
|
-
if ((cp >= 0 && cp <= 31 || cp >= 127 && cp <= 159) && cp !== 9 && cp !== 10 && cp !== 13) {
|
|
512
|
-
return "control-char";
|
|
513
|
-
}
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
var _VS_LO = 917760;
|
|
517
|
-
var _VS_HI = 917999;
|
|
518
|
-
function _isVS(cp) {
|
|
519
|
-
return cp >= _VS_LO && cp <= _VS_HI;
|
|
520
|
-
}
|
|
521
|
-
var _CP_NAME = {
|
|
522
|
-
173: "SOFT HYPHEN",
|
|
523
|
-
1564: "ARABIC LETTER MARK",
|
|
524
|
-
8203: "ZERO WIDTH SPACE",
|
|
525
|
-
8204: "ZERO WIDTH NON-JOINER",
|
|
526
|
-
8205: "ZERO WIDTH JOINER",
|
|
527
|
-
8206: "LEFT-TO-RIGHT MARK",
|
|
528
|
-
8207: "RIGHT-TO-LEFT MARK",
|
|
529
|
-
8234: "LEFT-TO-RIGHT EMBEDDING",
|
|
530
|
-
8235: "RIGHT-TO-LEFT EMBEDDING",
|
|
531
|
-
8236: "POP DIRECTIONAL FORMATTING",
|
|
532
|
-
8237: "LEFT-TO-RIGHT OVERRIDE",
|
|
533
|
-
8238: "RIGHT-TO-LEFT OVERRIDE",
|
|
534
|
-
8288: "WORD JOINER",
|
|
535
|
-
8294: "LEFT-TO-RIGHT ISOLATE",
|
|
536
|
-
8295: "RIGHT-TO-LEFT ISOLATE",
|
|
537
|
-
8296: "FIRST STRONG ISOLATE",
|
|
538
|
-
8297: "POP DIRECTIONAL ISOLATE",
|
|
539
|
-
8298: "INHIBIT SYMMETRIC SWAPPING",
|
|
540
|
-
8299: "ACTIVATE SYMMETRIC SWAPPING",
|
|
541
|
-
8300: "INHIBIT ARABIC FORM SHAPING",
|
|
542
|
-
8301: "ACTIVATE ARABIC FORM SHAPING",
|
|
543
|
-
8302: "NATIONAL DIGIT SHAPES",
|
|
544
|
-
8303: "NOMINAL DIGIT SHAPES",
|
|
545
|
-
65279: "ZERO WIDTH NO-BREAK SPACE",
|
|
546
|
-
65529: "INTERLINEAR ANNOTATION ANCHOR",
|
|
547
|
-
65530: "INTERLINEAR ANNOTATION SEPARATOR",
|
|
548
|
-
65531: "INTERLINEAR ANNOTATION TERMINATOR",
|
|
549
|
-
917505: "LANGUAGE TAG",
|
|
550
|
-
917536: "TAG SPACE",
|
|
551
|
-
917537: "TAG EXCLAMATION MARK",
|
|
552
|
-
917538: "TAG QUOTATION MARK",
|
|
553
|
-
917539: "TAG NUMBER SIGN",
|
|
554
|
-
917540: "TAG DOLLAR SIGN",
|
|
555
|
-
917541: "TAG PERCENT SIGN",
|
|
556
|
-
917542: "TAG AMPERSAND",
|
|
557
|
-
917543: "TAG APOSTROPHE",
|
|
558
|
-
917544: "TAG LEFT PARENTHESIS",
|
|
559
|
-
917545: "TAG RIGHT PARENTHESIS",
|
|
560
|
-
917546: "TAG ASTERISK",
|
|
561
|
-
917547: "TAG PLUS SIGN",
|
|
562
|
-
917548: "TAG COMMA",
|
|
563
|
-
917549: "TAG HYPHEN-MINUS",
|
|
564
|
-
917550: "TAG FULL STOP",
|
|
565
|
-
917551: "TAG SOLIDUS",
|
|
566
|
-
917552: "TAG DIGIT ZERO",
|
|
567
|
-
917553: "TAG DIGIT ONE",
|
|
568
|
-
917554: "TAG DIGIT TWO",
|
|
569
|
-
917555: "TAG DIGIT THREE",
|
|
570
|
-
917556: "TAG DIGIT FOUR",
|
|
571
|
-
917557: "TAG DIGIT FIVE",
|
|
572
|
-
917558: "TAG DIGIT SIX",
|
|
573
|
-
917559: "TAG DIGIT SEVEN",
|
|
574
|
-
917560: "TAG DIGIT EIGHT",
|
|
575
|
-
917561: "TAG DIGIT NINE",
|
|
576
|
-
917562: "TAG COLON",
|
|
577
|
-
917563: "TAG SEMICOLON",
|
|
578
|
-
917564: "TAG LESS-THAN SIGN",
|
|
579
|
-
917565: "TAG EQUALS SIGN",
|
|
580
|
-
917566: "TAG GREATER-THAN SIGN",
|
|
581
|
-
917567: "TAG QUESTION MARK",
|
|
582
|
-
917568: "TAG COMMERCIAL AT",
|
|
583
|
-
917569: "TAG LATIN CAPITAL LETTER A",
|
|
584
|
-
917570: "TAG LATIN CAPITAL LETTER B",
|
|
585
|
-
917571: "TAG LATIN CAPITAL LETTER C",
|
|
586
|
-
917572: "TAG LATIN CAPITAL LETTER D",
|
|
587
|
-
917573: "TAG LATIN CAPITAL LETTER E",
|
|
588
|
-
917574: "TAG LATIN CAPITAL LETTER F",
|
|
589
|
-
917575: "TAG LATIN CAPITAL LETTER G",
|
|
590
|
-
917576: "TAG LATIN CAPITAL LETTER H",
|
|
591
|
-
917577: "TAG LATIN CAPITAL LETTER I",
|
|
592
|
-
917578: "TAG LATIN CAPITAL LETTER J",
|
|
593
|
-
917579: "TAG LATIN CAPITAL LETTER K",
|
|
594
|
-
917580: "TAG LATIN CAPITAL LETTER L",
|
|
595
|
-
917581: "TAG LATIN CAPITAL LETTER M",
|
|
596
|
-
917582: "TAG LATIN CAPITAL LETTER N",
|
|
597
|
-
917583: "TAG LATIN CAPITAL LETTER O",
|
|
598
|
-
917584: "TAG LATIN CAPITAL LETTER P",
|
|
599
|
-
917585: "TAG LATIN CAPITAL LETTER Q",
|
|
600
|
-
917586: "TAG LATIN CAPITAL LETTER R",
|
|
601
|
-
917587: "TAG LATIN CAPITAL LETTER S",
|
|
602
|
-
917588: "TAG LATIN CAPITAL LETTER T",
|
|
603
|
-
917589: "TAG LATIN CAPITAL LETTER U",
|
|
604
|
-
917590: "TAG LATIN CAPITAL LETTER V",
|
|
605
|
-
917591: "TAG LATIN CAPITAL LETTER W",
|
|
606
|
-
917592: "TAG LATIN CAPITAL LETTER X",
|
|
607
|
-
917593: "TAG LATIN CAPITAL LETTER Y",
|
|
608
|
-
917594: "TAG LATIN CAPITAL LETTER Z",
|
|
609
|
-
917595: "TAG LEFT SQUARE BRACKET",
|
|
610
|
-
917596: "TAG REVERSE SOLIDUS",
|
|
611
|
-
917597: "TAG RIGHT SQUARE BRACKET",
|
|
612
|
-
917598: "TAG CIRCUMFLEX ACCENT",
|
|
613
|
-
917599: "TAG LOW LINE",
|
|
614
|
-
917600: "TAG GRAVE ACCENT",
|
|
615
|
-
917601: "TAG LATIN SMALL LETTER A",
|
|
616
|
-
917602: "TAG LATIN SMALL LETTER B",
|
|
617
|
-
917603: "TAG LATIN SMALL LETTER C",
|
|
618
|
-
917604: "TAG LATIN SMALL LETTER D",
|
|
619
|
-
917605: "TAG LATIN SMALL LETTER E",
|
|
620
|
-
917606: "TAG LATIN SMALL LETTER F",
|
|
621
|
-
917607: "TAG LATIN SMALL LETTER G",
|
|
622
|
-
917608: "TAG LATIN SMALL LETTER H",
|
|
623
|
-
917609: "TAG LATIN SMALL LETTER I",
|
|
624
|
-
917610: "TAG LATIN SMALL LETTER J",
|
|
625
|
-
917611: "TAG LATIN SMALL LETTER K",
|
|
626
|
-
917612: "TAG LATIN SMALL LETTER L",
|
|
627
|
-
917613: "TAG LATIN SMALL LETTER M",
|
|
628
|
-
917614: "TAG LATIN SMALL LETTER N",
|
|
629
|
-
917615: "TAG LATIN SMALL LETTER O",
|
|
630
|
-
917616: "TAG LATIN SMALL LETTER P",
|
|
631
|
-
917617: "TAG LATIN SMALL LETTER Q",
|
|
632
|
-
917618: "TAG LATIN SMALL LETTER R",
|
|
633
|
-
917619: "TAG LATIN SMALL LETTER S",
|
|
634
|
-
917620: "TAG LATIN SMALL LETTER T",
|
|
635
|
-
917621: "TAG LATIN SMALL LETTER U",
|
|
636
|
-
917622: "TAG LATIN SMALL LETTER V",
|
|
637
|
-
917623: "TAG LATIN SMALL LETTER W",
|
|
638
|
-
917624: "TAG LATIN SMALL LETTER X",
|
|
639
|
-
917625: "TAG LATIN SMALL LETTER Y",
|
|
640
|
-
917626: "TAG LATIN SMALL LETTER Z",
|
|
641
|
-
917627: "TAG LEFT CURLY BRACKET",
|
|
642
|
-
917628: "TAG VERTICAL LINE",
|
|
643
|
-
917629: "TAG RIGHT CURLY BRACKET",
|
|
644
|
-
917630: "TAG TILDE",
|
|
645
|
-
917631: "CANCEL TAG"
|
|
646
|
-
};
|
|
647
|
-
function _cpName(cp) {
|
|
648
|
-
const n = _CP_NAME[cp];
|
|
649
|
-
return n !== void 0 ? n : "<unnamed>";
|
|
650
|
-
}
|
|
651
|
-
function _scan(sf) {
|
|
652
|
-
if (sf.pragma_allows(CHECK)) {
|
|
653
|
-
return [];
|
|
654
|
-
}
|
|
655
|
-
const out = [];
|
|
656
|
-
for (const [lineno, text] of sf.iter_lines({ skip_example_fence: true })) {
|
|
657
|
-
let vs_run = 0;
|
|
658
|
-
for (const ch of text) {
|
|
659
|
-
const cp = ch.codePointAt(0);
|
|
660
|
-
if (_isVS(cp)) {
|
|
661
|
-
vs_run += 1;
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
const kind = _classify(cp);
|
|
665
|
-
if (kind) {
|
|
666
|
-
const name = _cpName(cp);
|
|
667
|
-
out.push(
|
|
668
|
-
new Finding(
|
|
669
|
-
sf.rel,
|
|
670
|
-
lineno,
|
|
671
|
-
CHECK,
|
|
672
|
-
"HIGH",
|
|
673
|
-
`${kind} U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${name})`,
|
|
674
|
-
sf.weight
|
|
675
|
-
)
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
if (vs_run >= 3) {
|
|
680
|
-
out.push(
|
|
681
|
-
new Finding(
|
|
682
|
-
sf.rel,
|
|
683
|
-
lineno,
|
|
684
|
-
CHECK,
|
|
685
|
-
"HIGH",
|
|
686
|
-
`variation-selector run x${vs_run} (steganography signature)`,
|
|
687
|
-
sf.weight
|
|
688
|
-
)
|
|
689
|
-
);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
return out;
|
|
693
|
-
}
|
|
694
|
-
var _BINARY_EXT = /* @__PURE__ */ new Set([
|
|
695
|
-
"png",
|
|
696
|
-
"jpg",
|
|
697
|
-
"jpeg",
|
|
698
|
-
"gif",
|
|
699
|
-
"ico",
|
|
700
|
-
"webp",
|
|
701
|
-
"avif",
|
|
702
|
-
"bmp",
|
|
703
|
-
"tiff",
|
|
704
|
-
"pdf",
|
|
705
|
-
"woff",
|
|
706
|
-
"woff2",
|
|
707
|
-
"ttf",
|
|
708
|
-
"otf",
|
|
709
|
-
"eot",
|
|
710
|
-
"zip",
|
|
711
|
-
"gz",
|
|
712
|
-
"tgz",
|
|
713
|
-
"bz2",
|
|
714
|
-
"xz",
|
|
715
|
-
"zst",
|
|
716
|
-
"7z",
|
|
717
|
-
"rar",
|
|
718
|
-
"wasm",
|
|
719
|
-
"mp3",
|
|
720
|
-
"mp4",
|
|
721
|
-
"wav",
|
|
722
|
-
"ogg",
|
|
723
|
-
"webm",
|
|
724
|
-
"mov",
|
|
725
|
-
"avi",
|
|
726
|
-
"so",
|
|
727
|
-
"dylib",
|
|
728
|
-
"dll",
|
|
729
|
-
"node",
|
|
730
|
-
"a",
|
|
731
|
-
"o",
|
|
732
|
-
"exe",
|
|
733
|
-
"jar",
|
|
734
|
-
"class",
|
|
735
|
-
"pyc",
|
|
736
|
-
"pyo",
|
|
737
|
-
"db",
|
|
738
|
-
"sqlite",
|
|
739
|
-
"sqlite3",
|
|
740
|
-
"bin",
|
|
741
|
-
"dat",
|
|
742
|
-
"docx",
|
|
743
|
-
"xlsx",
|
|
744
|
-
"pptx",
|
|
745
|
-
"doc",
|
|
746
|
-
"xls",
|
|
747
|
-
"ppt",
|
|
748
|
-
"odt",
|
|
749
|
-
"ods",
|
|
750
|
-
"odp"
|
|
751
|
-
]);
|
|
752
|
-
var _GENERATED_PREFIXES = [
|
|
753
|
-
"dist/",
|
|
754
|
-
".augment/",
|
|
755
|
-
".claude/",
|
|
756
|
-
".cursor/",
|
|
757
|
-
".clinerules/",
|
|
758
|
-
// Verbatim captured tool output, not authored text. These are RECORDINGS:
|
|
759
|
-
// escaping a control byte inside one would falsify the record it exists to
|
|
760
|
-
// preserve. If a capture needs to stay grep-readable, the fix is to escape
|
|
761
|
-
// at capture time — never to edit the artifact afterwards.
|
|
762
|
-
"agents/evidence/analysis/",
|
|
763
|
-
// The frozen hostile-encoding corpus. Its positives ARE raw control
|
|
764
|
-
// characters — that is the thing under test, so flagging them would demand
|
|
765
|
-
// escaping the fixtures and thereby delete the test. The corpus has its own,
|
|
766
|
-
// stronger integrity controls: a sha256 freeze plus a scope-guard test that
|
|
767
|
-
// is itself falsified by an out-of-scope fixture. Excluded here because it
|
|
768
|
-
// is DATA, on the same principle as the captures above.
|
|
769
|
-
"internal/bench/corpora/encoding-channels/"
|
|
770
|
-
];
|
|
771
|
-
function _isGenerated(rel) {
|
|
772
|
-
return _GENERATED_PREFIXES.some((p) => rel.startsWith(p));
|
|
773
|
-
}
|
|
774
|
-
function _isRawControlCodepoint(cp) {
|
|
775
|
-
if (cp === 9 || cp === 10 || cp === 13) return false;
|
|
776
|
-
return cp <= 31 || cp >= 127 && cp <= 159;
|
|
777
|
-
}
|
|
778
|
-
function _extensionOf(rel) {
|
|
779
|
-
const base = rel.slice(rel.lastIndexOf("/") + 1);
|
|
780
|
-
const dot = base.lastIndexOf(".");
|
|
781
|
-
return dot <= 0 ? "" : base.slice(dot + 1).toLowerCase();
|
|
782
|
-
}
|
|
783
|
-
function _trackedFiles() {
|
|
784
|
-
const res = spawnSync("git", ["ls-files", "-z"], {
|
|
785
|
-
cwd: ROOT,
|
|
786
|
-
maxBuffer: 64 * 1024 * 1024
|
|
787
|
-
});
|
|
788
|
-
if (res.status !== 0 || res.stdout === null) return null;
|
|
789
|
-
return res.stdout.toString("utf-8").split("\0").filter((p) => p !== "");
|
|
790
|
-
}
|
|
791
|
-
function _eligibleSourceFiles() {
|
|
792
|
-
const tracked = _trackedFiles();
|
|
793
|
-
if (tracked === null) return null;
|
|
794
|
-
return tracked.filter((rel) => !_isGenerated(rel) && !_BINARY_EXT.has(_extensionOf(rel)));
|
|
795
|
-
}
|
|
796
|
-
function _scanSourceControlBytes(files = null) {
|
|
797
|
-
const tracked = files ?? _eligibleSourceFiles();
|
|
798
|
-
if (tracked === null) return [];
|
|
799
|
-
const out = [];
|
|
800
|
-
for (const rel of tracked) {
|
|
801
|
-
if (_isGenerated(rel)) continue;
|
|
802
|
-
if (_BINARY_EXT.has(_extensionOf(rel))) continue;
|
|
803
|
-
let data;
|
|
804
|
-
try {
|
|
805
|
-
data = fs2.readFileSync(path2.join(ROOT, rel));
|
|
806
|
-
} catch {
|
|
807
|
-
continue;
|
|
808
|
-
}
|
|
809
|
-
try {
|
|
810
|
-
new TextDecoder("utf-8", { fatal: true }).decode(data);
|
|
811
|
-
} catch {
|
|
812
|
-
continue;
|
|
813
|
-
}
|
|
814
|
-
let line = 1;
|
|
815
|
-
for (const ch of data.toString("utf-8")) {
|
|
816
|
-
const cp = ch.codePointAt(0);
|
|
817
|
-
if (cp === void 0) continue;
|
|
818
|
-
if (cp === 10) {
|
|
819
|
-
line += 1;
|
|
820
|
-
continue;
|
|
821
|
-
}
|
|
822
|
-
if (!_isRawControlCodepoint(cp)) continue;
|
|
823
|
-
const hex = cp.toString(16).toUpperCase().padStart(2, "0");
|
|
824
|
-
out.push(
|
|
825
|
-
new Finding(
|
|
826
|
-
rel,
|
|
827
|
-
line,
|
|
828
|
-
CHECK,
|
|
829
|
-
"HIGH",
|
|
830
|
-
`raw control character U+${hex} in a text source \u2014 makes grep/file(1)/git treat this file as binary and skip it silently; use the language escape (e.g. \\0, \\x7F) instead`,
|
|
831
|
-
1
|
|
832
|
-
)
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
return out;
|
|
837
|
-
}
|
|
838
|
-
function _sanitize(filePath) {
|
|
839
|
-
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
840
|
-
let cleaned = "";
|
|
841
|
-
for (const ch of raw) {
|
|
842
|
-
const cp = ch.codePointAt(0);
|
|
843
|
-
if (_classify(cp) === null && !_isVS(cp)) {
|
|
844
|
-
cleaned += ch;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
cleaned = cleaned.normalize("NFKC");
|
|
848
|
-
const out = filePath + ".sanitized";
|
|
849
|
-
fs2.writeFileSync(out, cleaned, "utf-8");
|
|
850
|
-
return out;
|
|
851
|
-
}
|
|
852
|
-
function _argError(msg) {
|
|
853
|
-
process.stderr.write("usage: lint_hidden_unicode [-h] [--json] [--fix]\n");
|
|
854
|
-
process.stderr.write(`lint_hidden_unicode: error: ${msg}
|
|
855
|
-
`);
|
|
856
|
-
process.exit(2);
|
|
857
|
-
}
|
|
858
|
-
function parse_args(argv) {
|
|
859
|
-
const out = { json: false, fix: false };
|
|
860
|
-
const extra = [];
|
|
861
|
-
for (const a of argv) {
|
|
862
|
-
if (a === "-h" || a === "--help") {
|
|
863
|
-
process.stdout.write("usage: lint_hidden_unicode [-h] [--json] [--fix]\n");
|
|
864
|
-
process.exit(0);
|
|
865
|
-
} else if (a === "--json") {
|
|
866
|
-
out.json = true;
|
|
867
|
-
} else if (a === "--fix") {
|
|
868
|
-
out.fix = true;
|
|
869
|
-
} else {
|
|
870
|
-
extra.push(a);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
if (extra.length > 0) {
|
|
874
|
-
_argError(`unrecognized arguments: ${extra.join(" ")}`);
|
|
875
|
-
}
|
|
876
|
-
return out;
|
|
877
|
-
}
|
|
878
|
-
function main(argv = null) {
|
|
879
|
-
const args = parse_args(argv ?? process.argv.slice(2));
|
|
880
|
-
const findings = [];
|
|
881
|
-
const flagged = /* @__PURE__ */ new Set();
|
|
882
|
-
for (const sf of iter_corpus()) {
|
|
883
|
-
const hits = _scan(sf);
|
|
884
|
-
for (const h of hits) {
|
|
885
|
-
findings.push(h);
|
|
886
|
-
}
|
|
887
|
-
if (hits.length > 0) {
|
|
888
|
-
flagged.add(sf.path);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
const sourceFiles = _eligibleSourceFiles();
|
|
892
|
-
for (const h of _scanSourceControlBytes(sourceFiles)) {
|
|
893
|
-
findings.push(h);
|
|
894
|
-
}
|
|
895
|
-
if (!args.json) {
|
|
896
|
-
const n = sourceFiles === null ? 0 : sourceFiles.length;
|
|
897
|
-
process.stdout.write(
|
|
898
|
-
sourceFiles === null ? " source pass: SKIPPED \u2014 `git ls-files` unavailable, 0 files read for raw control bytes\n" : ` source pass: ${n} tracked text file(s) read for raw C0 control bytes (generated trees + verbatim captures excluded)
|
|
899
|
-
`
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
if (args.fix) {
|
|
903
|
-
for (const p of [...flagged].sort()) {
|
|
904
|
-
const sanitized = _sanitize(p);
|
|
905
|
-
const rel = path2.relative(path2.resolve(ROOT), path2.resolve(sanitized));
|
|
906
|
-
process.stdout.write(` fixed \u2192 ${rel.split(path2.sep).join("/")}
|
|
907
|
-
`);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
if (args.json) {
|
|
911
|
-
const payload = py_json_dumps_indent2(findings.map((f) => f.toDict()));
|
|
912
|
-
process.stdout.write(payload + "\n");
|
|
913
|
-
return findings.some((f) => f.is_fail) ? 1 : 0;
|
|
914
|
-
}
|
|
915
|
-
return report(findings, { check_label: "hidden-unicode" });
|
|
916
|
-
}
|
|
917
|
-
function _isCliEntry() {
|
|
918
|
-
if (true) {
|
|
919
|
-
return false;
|
|
920
|
-
}
|
|
921
|
-
if (process.argv[1] === void 0) {
|
|
922
|
-
return false;
|
|
923
|
-
}
|
|
924
|
-
const argvUrl = pathToFileURL(path2.resolve(process.argv[1])).href;
|
|
925
|
-
if (import.meta.url === argvUrl) {
|
|
926
|
-
return true;
|
|
927
|
-
}
|
|
928
|
-
try {
|
|
929
|
-
const here = fs2.realpathSync(fileURLToPath2(import.meta.url));
|
|
930
|
-
const argv = fs2.realpathSync(path2.resolve(process.argv[1]));
|
|
931
|
-
return here === argv;
|
|
932
|
-
} catch {
|
|
933
|
-
return false;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
if (_isCliEntry()) {
|
|
937
|
-
process.exitCode = main();
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// src/scripts/_lib/confusables.ts
|
|
941
|
-
var _LATIN = new RegExp("\\p{Script=Latin}", "u");
|
|
942
|
-
var _CYRILLIC = new RegExp("\\p{Script=Cyrillic}", "u");
|
|
943
|
-
var _GREEK = new RegExp("\\p{Script=Greek}", "u");
|
|
944
|
-
var _LETTER = new RegExp("\\p{L}", "u");
|
|
945
|
-
|
|
946
|
-
// src/scripts/_lib/retrieval_sanitize.ts
|
|
947
|
-
var MAX_FIELD_CHARS = 8192;
|
|
948
|
-
var _INVISIBLE_FILLERS = /* @__PURE__ */ new Set([
|
|
949
|
-
12644,
|
|
950
|
-
// HANGUL FILLER
|
|
951
|
-
4447,
|
|
952
|
-
// HANGUL CHOSEONG FILLER
|
|
953
|
-
4448
|
|
954
|
-
// HANGUL JUNGSEONG FILLER
|
|
955
|
-
]);
|
|
956
|
-
function _isStrippableControl(cp) {
|
|
957
|
-
if (cp === 9 || cp === 10) return false;
|
|
958
|
-
if (cp <= 31) return true;
|
|
959
|
-
if (cp === 127) return true;
|
|
960
|
-
if (cp >= 128 && cp <= 159) return true;
|
|
961
|
-
return false;
|
|
962
|
-
}
|
|
963
|
-
function sanitize_text(s) {
|
|
964
|
-
let out = "";
|
|
965
|
-
for (const ch of s) {
|
|
966
|
-
const cp = ch.codePointAt(0);
|
|
967
|
-
if (cp === void 0) continue;
|
|
968
|
-
if (_classify(cp) !== null) continue;
|
|
969
|
-
if (_isStrippableControl(cp)) continue;
|
|
970
|
-
if (_INVISIBLE_FILLERS.has(cp)) continue;
|
|
971
|
-
out += ch;
|
|
972
|
-
}
|
|
973
|
-
return out.length > MAX_FIELD_CHARS ? out.slice(0, MAX_FIELD_CHARS) : out;
|
|
974
|
-
}
|
|
975
|
-
function sanitize_entry(entry) {
|
|
976
|
-
const out = {};
|
|
977
|
-
for (const [k, v] of Object.entries(entry)) {
|
|
978
|
-
out[k] = _sanitize_value(v);
|
|
979
|
-
}
|
|
980
|
-
return out;
|
|
981
|
-
}
|
|
982
|
-
function _sanitize_value(v) {
|
|
983
|
-
if (typeof v === "string") return sanitize_text(v);
|
|
984
|
-
if (Array.isArray(v)) return v.map((x) => typeof x === "string" ? sanitize_text(x) : _sanitize_value(x));
|
|
985
|
-
if (v && typeof v === "object" && v.constructor === Object) {
|
|
986
|
-
return sanitize_entry(v);
|
|
987
|
-
}
|
|
988
|
-
return v;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// src/scripts/_lib/lexical_index.ts
|
|
992
|
-
var BM25_K1 = 1.5;
|
|
993
|
-
var BM25_B = 0.75;
|
|
994
|
-
var MIN_TOKEN_LEN = 2;
|
|
995
|
-
function tokenize(s) {
|
|
996
|
-
const out = [];
|
|
997
|
-
for (const raw of s.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
998
|
-
if (raw.length >= MIN_TOKEN_LEN) out.push(raw);
|
|
999
|
-
}
|
|
1000
|
-
return out;
|
|
1001
|
-
}
|
|
1002
|
-
function trigrams(s) {
|
|
1003
|
-
const norm = s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
1004
|
-
const out = /* @__PURE__ */ new Set();
|
|
1005
|
-
if (norm.length < 3) {
|
|
1006
|
-
if (norm.length > 0) out.add(norm);
|
|
1007
|
-
return out;
|
|
1008
|
-
}
|
|
1009
|
-
for (let i = 0; i + 3 <= norm.length; i++) {
|
|
1010
|
-
out.add(norm.slice(i, i + 3));
|
|
1011
|
-
}
|
|
1012
|
-
return out;
|
|
1013
|
-
}
|
|
1014
|
-
var LexicalIndex = class {
|
|
1015
|
-
docs = /* @__PURE__ */ new Map();
|
|
1016
|
-
df = /* @__PURE__ */ new Map();
|
|
1017
|
-
n;
|
|
1018
|
-
avgdl;
|
|
1019
|
-
constructor(documents) {
|
|
1020
|
-
let totalLen = 0;
|
|
1021
|
-
for (const d of documents) {
|
|
1022
|
-
const tokens = tokenize(d.text);
|
|
1023
|
-
const tf = /* @__PURE__ */ new Map();
|
|
1024
|
-
for (const t of tokens) tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
1025
|
-
for (const t of tf.keys()) this.df.set(t, (this.df.get(t) ?? 0) + 1);
|
|
1026
|
-
this.docs.set(d.id, { id: d.id, len: tokens.length, tf, trigrams: trigrams(d.text) });
|
|
1027
|
-
totalLen += tokens.length;
|
|
1028
|
-
}
|
|
1029
|
-
this.n = documents.length;
|
|
1030
|
-
this.avgdl = this.n > 0 ? totalLen / this.n : 0;
|
|
1031
|
-
}
|
|
1032
|
-
/** Robertson–Spärck-Jones IDF, floored at 0 so common terms never subtract. */
|
|
1033
|
-
idf(term) {
|
|
1034
|
-
const df = this.df.get(term) ?? 0;
|
|
1035
|
-
if (df === 0) return 0;
|
|
1036
|
-
return Math.max(0, Math.log(1 + (this.n - df + 0.5) / (df + 0.5)));
|
|
1037
|
-
}
|
|
1038
|
-
/**
|
|
1039
|
-
* BM25 score of one document against the tokenised query terms, weighted by
|
|
1040
|
-
* **term coverage**: the raw BM25 sum is multiplied by `coverage²`, where
|
|
1041
|
-
* `coverage = distinct-query-terms-matched / distinct-query-terms`. This
|
|
1042
|
-
* stops a single generic, high-TF term match from outranking a document that
|
|
1043
|
-
* matches several rarer query terms once each (Phase 1 refinement). A
|
|
1044
|
-
* single-term query has `coverage == 1`, so its score is unchanged — the
|
|
1045
|
-
* weighting only re-orders genuinely multi-term queries.
|
|
1046
|
-
*/
|
|
1047
|
-
score(queryTerms, docId) {
|
|
1048
|
-
const doc = this.docs.get(docId);
|
|
1049
|
-
if (doc === void 0 || doc.len === 0) return 0;
|
|
1050
|
-
let s = 0;
|
|
1051
|
-
const matched = /* @__PURE__ */ new Set();
|
|
1052
|
-
for (const term of queryTerms) {
|
|
1053
|
-
const tf = doc.tf.get(term);
|
|
1054
|
-
if (tf === void 0) continue;
|
|
1055
|
-
const idf = this.idf(term);
|
|
1056
|
-
if (idf === 0) continue;
|
|
1057
|
-
const denom = tf + BM25_K1 * (1 - BM25_B + BM25_B * doc.len / (this.avgdl || 1));
|
|
1058
|
-
s += idf * (tf * (BM25_K1 + 1)) / denom;
|
|
1059
|
-
matched.add(term);
|
|
1060
|
-
}
|
|
1061
|
-
if (s === 0) return 0;
|
|
1062
|
-
const distinctQuery = new Set(queryTerms).size;
|
|
1063
|
-
const coverage = distinctQuery > 0 ? matched.size / distinctQuery : 1;
|
|
1064
|
-
return s * coverage * coverage;
|
|
1065
|
-
}
|
|
1066
|
-
/**
|
|
1067
|
-
* Rank documents for a set of query keys (phrases are tokenised). A
|
|
1068
|
-
* character-trigram prefilter selects candidates cheaply; the guard keeps
|
|
1069
|
-
* any doc sharing a query TOKEN even when no trigram overlaps, so the
|
|
1070
|
-
* prefilter never drops a genuine lexical match. Ties break by id for a
|
|
1071
|
-
* stable, deterministic order.
|
|
1072
|
-
*/
|
|
1073
|
-
rank(queryKeys) {
|
|
1074
|
-
const queryText = queryKeys.join(" ");
|
|
1075
|
-
const qTerms = tokenize(queryText);
|
|
1076
|
-
const qTermSet = new Set(qTerms);
|
|
1077
|
-
const qTrigrams = trigrams(queryText);
|
|
1078
|
-
const candidates = [];
|
|
1079
|
-
for (const [id, doc] of this.docs) {
|
|
1080
|
-
let hit = false;
|
|
1081
|
-
for (const t of qTrigrams) {
|
|
1082
|
-
if (doc.trigrams.has(t)) {
|
|
1083
|
-
hit = true;
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
if (!hit) {
|
|
1088
|
-
for (const t of qTermSet) {
|
|
1089
|
-
if (doc.tf.has(t)) {
|
|
1090
|
-
hit = true;
|
|
1091
|
-
break;
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
if (hit) candidates.push(id);
|
|
1096
|
-
}
|
|
1097
|
-
const scored = candidates.map((id) => ({ id, score: this.score(qTerms, id) })).filter((r) => r.score > 0);
|
|
1098
|
-
scored.sort((a, b) => b.score - a.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
1099
|
-
return scored;
|
|
1100
|
-
}
|
|
1101
|
-
/** Document count — for the caller's tripwire / diagnostics. */
|
|
1102
|
-
get size() {
|
|
1103
|
-
return this.n;
|
|
1104
|
-
}
|
|
1105
|
-
};
|
|
1106
|
-
|
|
1107
|
-
// src/scripts/_lib/memory_fts_index.ts
|
|
1108
|
-
import * as fs5 from "node:fs";
|
|
1109
|
-
import * as path5 from "node:path";
|
|
1110
|
-
|
|
1111
|
-
// src/scripts/_lib/sqlite_guard.ts
|
|
1112
|
-
import { createRequire } from "node:module";
|
|
1113
|
-
var SqliteUnavailableError = class extends Error {
|
|
1114
|
-
constructor(callerName, cause) {
|
|
1115
|
-
super(
|
|
1116
|
-
`node:sqlite is unavailable in this runtime (${cause}). ${callerName} requires Node with the built-in SQLite module.`
|
|
1117
|
-
);
|
|
1118
|
-
this.name = "SqliteUnavailableError";
|
|
1119
|
-
}
|
|
1120
|
-
};
|
|
1121
|
-
var _sqliteWarningSilenced = false;
|
|
1122
|
-
function _silenceSqliteExperimentalWarning() {
|
|
1123
|
-
if (_sqliteWarningSilenced) return;
|
|
1124
|
-
_sqliteWarningSilenced = true;
|
|
1125
|
-
const orig = process.emitWarning.bind(process);
|
|
1126
|
-
process.emitWarning = ((warning, ...rest) => {
|
|
1127
|
-
const text = typeof warning === "string" ? warning : warning?.message ?? "";
|
|
1128
|
-
if (/SQLite is an experimental/i.test(text)) return;
|
|
1129
|
-
orig(warning, ...rest);
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
var _require;
|
|
1133
|
-
function _syncRequire() {
|
|
1134
|
-
if (_require === void 0) {
|
|
1135
|
-
_require = createRequire(import.meta.url);
|
|
1136
|
-
}
|
|
1137
|
-
return _require;
|
|
1138
|
-
}
|
|
1139
|
-
function loadSqliteSync(callerName) {
|
|
1140
|
-
_silenceSqliteExperimentalWarning();
|
|
1141
|
-
try {
|
|
1142
|
-
return _syncRequire()("node:sqlite");
|
|
1143
|
-
} catch (exc) {
|
|
1144
|
-
const message = exc instanceof Error ? exc.message : String(exc);
|
|
1145
|
-
throw new SqliteUnavailableError(callerName, message);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
function probeFts5Sync() {
|
|
1149
|
-
let sqlite;
|
|
1150
|
-
try {
|
|
1151
|
-
sqlite = loadSqliteSync("sqlite_guard.probeFts5Sync");
|
|
1152
|
-
} catch {
|
|
1153
|
-
return false;
|
|
1154
|
-
}
|
|
1155
|
-
let conn = null;
|
|
1156
|
-
try {
|
|
1157
|
-
conn = new sqlite.DatabaseSync(":memory:");
|
|
1158
|
-
conn.exec("CREATE VIRTUAL TABLE _fts5_probe USING fts5(body)");
|
|
1159
|
-
conn.exec("INSERT INTO _fts5_probe(body) VALUES ('probe')");
|
|
1160
|
-
const row = conn.prepare("SELECT body FROM _fts5_probe WHERE _fts5_probe MATCH 'probe'").get();
|
|
1161
|
-
return row !== void 0;
|
|
1162
|
-
} catch {
|
|
1163
|
-
return false;
|
|
1164
|
-
} finally {
|
|
1165
|
-
try {
|
|
1166
|
-
conn?.close();
|
|
1167
|
-
} catch {
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
function readUserVersion(conn) {
|
|
1172
|
-
const row = conn.prepare("PRAGMA user_version").get();
|
|
1173
|
-
const v = row?.["user_version"];
|
|
1174
|
-
if (typeof v === "bigint") return Number(v);
|
|
1175
|
-
if (typeof v === "number") return v;
|
|
1176
|
-
return 0;
|
|
1177
|
-
}
|
|
1178
|
-
function stampUserVersion(conn, version) {
|
|
1179
|
-
if (!Number.isInteger(version) || version < 0 || version > 2 ** 31 - 1) {
|
|
1180
|
-
throw new RangeError(`stampUserVersion: version out of range: ${version}`);
|
|
1181
|
-
}
|
|
1182
|
-
conn.exec(`PRAGMA user_version = ${version}`);
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
// src/scripts/memory_report.ts
|
|
1186
|
-
var import_yaml = __toESM(require_dist(), 1);
|
|
1187
|
-
import * as fs4 from "node:fs";
|
|
1188
|
-
import * as path4 from "node:path";
|
|
1189
|
-
import process3 from "node:process";
|
|
1190
|
-
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL3 } from "node:url";
|
|
1191
|
-
|
|
1192
|
-
// src/scripts/memory_status.ts
|
|
1193
|
-
import process2 from "node:process";
|
|
1194
|
-
import * as fs3 from "node:fs";
|
|
1195
|
-
import * as path3 from "node:path";
|
|
1196
|
-
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
1197
|
-
var CONTRACT_VERSION = 1;
|
|
1198
|
-
var _FILE_BACKEND_VERSION = "0.0.0-file";
|
|
1199
|
-
var _FILE_BACKEND_FEATURES = ["file-fallback"];
|
|
1200
|
-
function status(_refresh = false) {
|
|
1201
|
-
return {
|
|
1202
|
-
status: "file",
|
|
1203
|
-
// always "file" — no external backend
|
|
1204
|
-
backend: "file",
|
|
1205
|
-
reason: "file-backed memory (no external backend)",
|
|
1206
|
-
elapsed_ms: 0
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
function health(_refresh = false) {
|
|
1210
|
-
return {
|
|
1211
|
-
contract_version: CONTRACT_VERSION,
|
|
1212
|
-
status: "ok",
|
|
1213
|
-
backend_version: _FILE_BACKEND_VERSION,
|
|
1214
|
-
features: [..._FILE_BACKEND_FEATURES]
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
function _pyJsonDumps(value) {
|
|
1218
|
-
return _escapeNonAscii(_pyCompactSeparators(JSON.stringify(value)));
|
|
1219
|
-
}
|
|
1220
|
-
function _pyCompactSeparators(json) {
|
|
1221
|
-
let result = "";
|
|
1222
|
-
let inString = false;
|
|
1223
|
-
let escaped = false;
|
|
1224
|
-
for (let i = 0; i < json.length; i += 1) {
|
|
1225
|
-
const ch = json[i];
|
|
1226
|
-
result += ch;
|
|
1227
|
-
if (escaped) {
|
|
1228
|
-
escaped = false;
|
|
1229
|
-
continue;
|
|
1230
|
-
}
|
|
1231
|
-
if (ch === "\\") {
|
|
1232
|
-
escaped = true;
|
|
1233
|
-
continue;
|
|
1234
|
-
}
|
|
1235
|
-
if (ch === '"') {
|
|
1236
|
-
inString = !inString;
|
|
1237
|
-
continue;
|
|
1238
|
-
}
|
|
1239
|
-
if (!inString && (ch === "," || ch === ":")) {
|
|
1240
|
-
result += " ";
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
return result;
|
|
1244
|
-
}
|
|
1245
|
-
function _escapeNonAscii(s) {
|
|
1246
|
-
let out = "";
|
|
1247
|
-
for (const ch of s) {
|
|
1248
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
1249
|
-
if (code > 127) {
|
|
1250
|
-
for (let i = 0; i < ch.length; i += 1) {
|
|
1251
|
-
out += `\\u${ch.charCodeAt(i).toString(16).padStart(4, "0")}`;
|
|
1252
|
-
}
|
|
1253
|
-
} else {
|
|
1254
|
-
out += ch;
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
return out;
|
|
1258
|
-
}
|
|
1259
|
-
var _PROG = "memory_status.py";
|
|
1260
|
-
var _USAGE = "usage: memory_status.py [-h] [--format {text,json}] [--refresh] [--health]\n";
|
|
1261
|
-
var _ExitError = class extends Error {
|
|
1262
|
-
constructor(code) {
|
|
1263
|
-
super(`exit ${code}`);
|
|
1264
|
-
this.code = code;
|
|
1265
|
-
}
|
|
1266
|
-
code;
|
|
1267
|
-
};
|
|
1268
|
-
function _usageError(msg) {
|
|
1269
|
-
process2.stderr.write(_USAGE);
|
|
1270
|
-
process2.stderr.write(`${_PROG}: error: ${msg}
|
|
1271
|
-
`);
|
|
1272
|
-
throw new _ExitError(2);
|
|
1273
|
-
}
|
|
1274
|
-
function _parseArgs(argv) {
|
|
1275
|
-
const args = { format: "text", refresh: false, health: false };
|
|
1276
|
-
const checkFormat = (v) => {
|
|
1277
|
-
if (v !== "text" && v !== "json") {
|
|
1278
|
-
_usageError(`argument --format: invalid choice: '${v}' (choose from 'text', 'json')`);
|
|
1279
|
-
}
|
|
1280
|
-
return v;
|
|
1281
|
-
};
|
|
1282
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
1283
|
-
const a = argv[i];
|
|
1284
|
-
const takeValue = (flag) => {
|
|
1285
|
-
const v = argv[++i];
|
|
1286
|
-
if (v === void 0) {
|
|
1287
|
-
_usageError(`argument ${flag}: expected one argument`);
|
|
1288
|
-
}
|
|
1289
|
-
return v;
|
|
1290
|
-
};
|
|
1291
|
-
if (a === "--format") {
|
|
1292
|
-
args.format = checkFormat(takeValue("--format"));
|
|
1293
|
-
} else if (a.startsWith("--format=")) {
|
|
1294
|
-
args.format = checkFormat(a.slice("--format=".length));
|
|
1295
|
-
} else if (a === "--refresh") {
|
|
1296
|
-
args.refresh = true;
|
|
1297
|
-
} else if (a === "--health") {
|
|
1298
|
-
args.health = true;
|
|
1299
|
-
} else if (a === "-h" || a === "--help") {
|
|
1300
|
-
process2.stdout.write(_USAGE);
|
|
1301
|
-
throw new _ExitError(0);
|
|
1302
|
-
} else {
|
|
1303
|
-
_usageError(`unrecognized arguments: ${a}`);
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
return args;
|
|
1307
|
-
}
|
|
1308
|
-
function main2() {
|
|
1309
|
-
const args = _parseArgs(process2.argv.slice(2));
|
|
1310
|
-
if (args.health) {
|
|
1311
|
-
process2.stdout.write(`${_pyJsonDumps(health())}
|
|
1312
|
-
`);
|
|
1313
|
-
return 0;
|
|
1314
|
-
}
|
|
1315
|
-
const r = status();
|
|
1316
|
-
if (args.format === "json") {
|
|
1317
|
-
process2.stdout.write(`${_pyJsonDumps(r)}
|
|
1318
|
-
`);
|
|
1319
|
-
} else {
|
|
1320
|
-
process2.stdout.write(` \u2139\uFE0F backend=${r.backend} status=${r.status} reason=${r.reason}
|
|
1321
|
-
`);
|
|
1322
|
-
}
|
|
1323
|
-
return 0;
|
|
1324
|
-
}
|
|
1325
|
-
function _isCliEntry2() {
|
|
1326
|
-
if (true) {
|
|
1327
|
-
return false;
|
|
1328
|
-
}
|
|
1329
|
-
if (process2.argv[1] === void 0) {
|
|
1330
|
-
return false;
|
|
1331
|
-
}
|
|
1332
|
-
const argvUrl = pathToFileURL2(path3.resolve(process2.argv[1])).href;
|
|
1333
|
-
if (import.meta.url === argvUrl) {
|
|
1334
|
-
return true;
|
|
1335
|
-
}
|
|
1336
|
-
try {
|
|
1337
|
-
const here = fs3.realpathSync(fileURLToPath3(import.meta.url));
|
|
1338
|
-
const argv = fs3.realpathSync(path3.resolve(process2.argv[1]));
|
|
1339
|
-
return here === argv;
|
|
1340
|
-
} catch {
|
|
1341
|
-
return false;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
var _isMain = _isCliEntry2();
|
|
1345
|
-
if (_isMain) {
|
|
1346
|
-
try {
|
|
1347
|
-
process2.exitCode = main2();
|
|
1348
|
-
} catch (e) {
|
|
1349
|
-
if (e instanceof _ExitError) {
|
|
1350
|
-
process2.exitCode = e.code;
|
|
1351
|
-
} else {
|
|
1352
|
-
throw e;
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
// src/scripts/memory_report.ts
|
|
1358
|
-
var MEMORY_ROOT = path4.join("agents", "memory");
|
|
1359
|
-
var INTAKE_ROOT = path4.join(MEMORY_ROOT, "intake");
|
|
1360
|
-
function _setMemoryRoot(p) {
|
|
1361
|
-
MEMORY_ROOT = p;
|
|
1362
|
-
}
|
|
1363
|
-
var CURATED_TYPES = [
|
|
1364
|
-
"ownership",
|
|
1365
|
-
"historical-patterns",
|
|
1366
|
-
"domain-invariants",
|
|
1367
|
-
"incident-learnings",
|
|
1368
|
-
"product-rules"
|
|
1369
|
-
];
|
|
1370
|
-
var _MODE_MARKER_PATTERN = /<!--\s*role-mode:\s*([a-z0-9][a-z0-9-]*)\s*\|\s*contract:[^>]*-->/g;
|
|
1371
|
-
var _MODE_SCAN_DIRS = [
|
|
1372
|
-
path4.join("agents", "sessions"),
|
|
1373
|
-
path4.join("agents", "runtime", "reports"),
|
|
1374
|
-
path4.join("agents", "handoffs"),
|
|
1375
|
-
path4.join("agents", "learnings")
|
|
1376
|
-
];
|
|
1377
|
-
var _KNOWN_MODES = /* @__PURE__ */ new Set([
|
|
1378
|
-
"developer",
|
|
1379
|
-
"reviewer",
|
|
1380
|
-
"tester",
|
|
1381
|
-
"po",
|
|
1382
|
-
"incident",
|
|
1383
|
-
"planner"
|
|
1384
|
-
]);
|
|
1385
|
-
var PYYAML_DATE_ONLY_RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
|
1386
|
-
var PYYAML_TIMESTAMP_RE = /^(?:[0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}(?:[Tt]|[ \t]+)[0-9]{1,2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]*)?(?:[ \t]*(?:Z|[-+][0-9]{1,2}(?::[0-9]{2})?))?)$/;
|
|
1387
|
-
var PyTimestamp = class {
|
|
1388
|
-
constructor(pyStr) {
|
|
1389
|
-
this.pyStr = pyStr;
|
|
1390
|
-
}
|
|
1391
|
-
pyStr;
|
|
1392
|
-
toString() {
|
|
1393
|
-
return this.pyStr;
|
|
1394
|
-
}
|
|
1395
|
-
};
|
|
1396
|
-
var PyFloat2 = class {
|
|
1397
|
-
constructor(value) {
|
|
1398
|
-
this.value = value;
|
|
1399
|
-
}
|
|
1400
|
-
value;
|
|
1401
|
-
};
|
|
1402
|
-
function _isPlainObject2(v) {
|
|
1403
|
-
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof PyTimestamp) && !(v instanceof PyFloat2);
|
|
1404
|
-
}
|
|
1405
|
-
function _isFile(p) {
|
|
1406
|
-
try {
|
|
1407
|
-
return fs4.statSync(p).isFile();
|
|
1408
|
-
} catch {
|
|
1409
|
-
return false;
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
function _isDir(p) {
|
|
1413
|
-
try {
|
|
1414
|
-
return fs4.statSync(p).isDirectory();
|
|
1415
|
-
} catch {
|
|
1416
|
-
return false;
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
function _load_yaml(p) {
|
|
1420
|
-
const text = fs4.readFileSync(p, "utf-8");
|
|
1421
|
-
const doc = (0, import_yaml.parseDocument)(text, { version: "1.1", prettyErrors: false });
|
|
1422
|
-
if (doc.errors.length > 0) {
|
|
1423
|
-
const err = doc.errors[0];
|
|
1424
|
-
throw new Error(err ? err.message : "YAML parse error");
|
|
1425
|
-
}
|
|
1426
|
-
import_yaml.default.visit(doc, {
|
|
1427
|
-
Scalar(_key, node) {
|
|
1428
|
-
if (!node.range) {
|
|
1429
|
-
return;
|
|
1430
|
-
}
|
|
1431
|
-
const raw = text.slice(node.range[0], node.range[1]);
|
|
1432
|
-
if (node.value instanceof Date) {
|
|
1433
|
-
if (PYYAML_DATE_ONLY_RE.test(raw)) {
|
|
1434
|
-
node.value = new PyTimestamp(raw);
|
|
1435
|
-
} else if (PYYAML_TIMESTAMP_RE.test(raw)) {
|
|
1436
|
-
node.value = new PyTimestamp(_pyYamlDatetimeStr(raw));
|
|
1437
|
-
}
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
if (typeof node.value === "boolean" && node.type === "PLAIN" && /^[ynYN]$/.test(raw)) {
|
|
1441
|
-
node.value = raw;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
});
|
|
1445
|
-
const data = doc.toJS({ mapAsMap: false });
|
|
1446
|
-
return data == null ? {} : data;
|
|
1447
|
-
}
|
|
1448
|
-
function _pad2(v) {
|
|
1449
|
-
return v.padStart(2, "0");
|
|
1450
|
-
}
|
|
1451
|
-
function _pyYamlDatetimeStr(raw) {
|
|
1452
|
-
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[Tt]|[ \t]+)(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d*))?(?:[ \t]*(Z|[-+]\d{1,2}(?::\d{2})?))?$/.exec(
|
|
1453
|
-
raw
|
|
1454
|
-
);
|
|
1455
|
-
if (!m) {
|
|
1456
|
-
return raw;
|
|
1457
|
-
}
|
|
1458
|
-
const [, y, mo, d, h, mi, s, frac, tz] = m;
|
|
1459
|
-
const date = `${y}-${_pad2(mo)}-${_pad2(d)}`;
|
|
1460
|
-
let time = `${_pad2(h)}:${mi}:${s}`;
|
|
1461
|
-
if (frac && /[1-9]/.test(frac)) {
|
|
1462
|
-
time += `.${(frac + "000000").slice(0, 6)}`;
|
|
1463
|
-
}
|
|
1464
|
-
let off = "";
|
|
1465
|
-
if (tz) {
|
|
1466
|
-
if (tz === "Z") {
|
|
1467
|
-
off = "+00:00";
|
|
1468
|
-
} else {
|
|
1469
|
-
const tm = /^([-+])(\d{1,2})(?::(\d{2}))?$/.exec(tz);
|
|
1470
|
-
if (tm) {
|
|
1471
|
-
off = `${tm[1]}${_pad2(tm[2])}:${tm[3] ?? "00"}`;
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
return `${date} ${time}${off}`;
|
|
1476
|
-
}
|
|
1477
|
-
function _iter_curated_entries() {
|
|
1478
|
-
const out = [];
|
|
1479
|
-
for (const mtype of CURATED_TYPES) {
|
|
1480
|
-
const single = path4.join(MEMORY_ROOT, `${mtype}.yml`);
|
|
1481
|
-
if (_isFile(single)) {
|
|
1482
|
-
const data = _load_yaml(single);
|
|
1483
|
-
const entries = data["entries"];
|
|
1484
|
-
if (Array.isArray(entries)) {
|
|
1485
|
-
for (const e of entries) {
|
|
1486
|
-
if (_isPlainObject2(e)) {
|
|
1487
|
-
out.push([single, mtype, e]);
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
const type_dir = path4.join(MEMORY_ROOT, mtype);
|
|
1493
|
-
if (_isDir(type_dir)) {
|
|
1494
|
-
for (const yml of _rglobYml(type_dir)) {
|
|
1495
|
-
const data = _load_yaml(yml) ?? {};
|
|
1496
|
-
const entries = data["entries"];
|
|
1497
|
-
if (Array.isArray(entries)) {
|
|
1498
|
-
for (const e of entries) {
|
|
1499
|
-
if (_isPlainObject2(e)) {
|
|
1500
|
-
out.push([yml, mtype, e]);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
} else if (_isPlainObject2(data) && data["id"]) {
|
|
1504
|
-
out.push([yml, mtype, data]);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
return out;
|
|
1510
|
-
}
|
|
1511
|
-
function _rglobYml(root) {
|
|
1512
|
-
const found = [];
|
|
1513
|
-
const walk = (dir) => {
|
|
1514
|
-
let entries;
|
|
1515
|
-
try {
|
|
1516
|
-
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
1517
|
-
} catch {
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
1520
|
-
for (const ent of entries) {
|
|
1521
|
-
const full = path4.join(dir, ent.name);
|
|
1522
|
-
if (ent.isDirectory()) {
|
|
1523
|
-
walk(full);
|
|
1524
|
-
} else if (ent.isFile() && ent.name.endsWith(".yml")) {
|
|
1525
|
-
found.push(full);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
};
|
|
1529
|
-
walk(root);
|
|
1530
|
-
found.sort();
|
|
1531
|
-
return found;
|
|
1532
|
-
}
|
|
1533
|
-
function _globJsonl(root) {
|
|
1534
|
-
let names;
|
|
1535
|
-
try {
|
|
1536
|
-
names = fs4.readdirSync(root);
|
|
1537
|
-
} catch {
|
|
1538
|
-
return [];
|
|
1539
|
-
}
|
|
1540
|
-
return names.filter((n) => n.endsWith(".jsonl")).sort().map((n) => path4.join(root, n));
|
|
1541
|
-
}
|
|
1542
|
-
function _intake_stats() {
|
|
1543
|
-
let total = 0;
|
|
1544
|
-
const by_type = /* @__PURE__ */ new Map();
|
|
1545
|
-
const by_month = /* @__PURE__ */ new Map();
|
|
1546
|
-
let superseded = 0;
|
|
1547
|
-
if (_isDir(INTAKE_ROOT)) {
|
|
1548
|
-
for (const jsonl of _globJsonl(INTAKE_ROOT)) {
|
|
1549
|
-
const stem = path4.basename(jsonl).replace(/\.jsonl$/, "");
|
|
1550
|
-
const month = stem.replace("signals-", "");
|
|
1551
|
-
for (const obj of _readJsonl(jsonl)) {
|
|
1552
|
-
if (obj["type"] === "supersede") {
|
|
1553
|
-
superseded += 1;
|
|
1554
|
-
continue;
|
|
1555
|
-
}
|
|
1556
|
-
total += 1;
|
|
1557
|
-
const t = obj["entry_type"];
|
|
1558
|
-
if (typeof t === "string") {
|
|
1559
|
-
by_type.set(t, (by_type.get(t) ?? 0) + 1);
|
|
1560
|
-
}
|
|
1561
|
-
by_month.set(month, (by_month.get(month) ?? 0) + 1);
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
return {
|
|
1566
|
-
total_active: total,
|
|
1567
|
-
superseded,
|
|
1568
|
-
by_type: _mapToObj(by_type),
|
|
1569
|
-
by_month: _mapToObj(by_month)
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
function _readJsonl(p) {
|
|
1573
|
-
let text;
|
|
1574
|
-
try {
|
|
1575
|
-
text = fs4.readFileSync(p, "utf-8");
|
|
1576
|
-
} catch {
|
|
1577
|
-
return [];
|
|
1578
|
-
}
|
|
1579
|
-
const out = [];
|
|
1580
|
-
for (const rawLine of text.split("\n")) {
|
|
1581
|
-
const line = rawLine.trim();
|
|
1582
|
-
if (!line) {
|
|
1583
|
-
continue;
|
|
1584
|
-
}
|
|
1585
|
-
let obj;
|
|
1586
|
-
try {
|
|
1587
|
-
obj = JSON.parse(line);
|
|
1588
|
-
} catch {
|
|
1589
|
-
continue;
|
|
1590
|
-
}
|
|
1591
|
-
if (_isPlainObject2(obj)) {
|
|
1592
|
-
out.push(obj);
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
return out;
|
|
1596
|
-
}
|
|
1597
|
-
function _mapToObj(m) {
|
|
1598
|
-
const obj = {};
|
|
1599
|
-
for (const [k, v] of m) {
|
|
1600
|
-
obj[k] = v;
|
|
1601
|
-
}
|
|
1602
|
-
return obj;
|
|
1603
|
-
}
|
|
1604
|
-
function _todayDate() {
|
|
1605
|
-
const now = /* @__PURE__ */ new Date();
|
|
1606
|
-
return { y: now.getFullYear(), m: now.getMonth() + 1, d: now.getDate() };
|
|
1607
|
-
}
|
|
1608
|
-
function _dateDiffDays(a, b) {
|
|
1609
|
-
const am = Date.UTC(a.y, a.m - 1, a.d);
|
|
1610
|
-
const bm = Date.UTC(b.y, b.m - 1, b.d);
|
|
1611
|
-
return Math.round((am - bm) / (24 * 60 * 60 * 1e3));
|
|
1612
|
-
}
|
|
1613
|
-
function _parseIsoDate(s) {
|
|
1614
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
1615
|
-
if (!m) {
|
|
1616
|
-
return null;
|
|
1617
|
-
}
|
|
1618
|
-
const y = Number(m[1]);
|
|
1619
|
-
const mo = Number(m[2]);
|
|
1620
|
-
const d = Number(m[3]);
|
|
1621
|
-
if (mo < 1 || mo > 12 || d < 1 || d > 31) {
|
|
1622
|
-
return null;
|
|
1623
|
-
}
|
|
1624
|
-
return { y, m: mo, d };
|
|
1625
|
-
}
|
|
1626
|
-
function _addDays(date, days) {
|
|
1627
|
-
const ms = Date.UTC(date.y, date.m - 1, date.d) + days * 24 * 60 * 60 * 1e3;
|
|
1628
|
-
const dt = new Date(ms);
|
|
1629
|
-
return { y: dt.getUTCFullYear(), m: dt.getUTCMonth() + 1, d: dt.getUTCDate() };
|
|
1630
|
-
}
|
|
1631
|
-
function _staleness_report() {
|
|
1632
|
-
const today = _todayDate();
|
|
1633
|
-
const stale = [];
|
|
1634
|
-
for (const [p, mtype, entry] of _iter_curated_entries()) {
|
|
1635
|
-
const lv = entry["last_validated"];
|
|
1636
|
-
const review_after = entry["review_after_days"];
|
|
1637
|
-
const lvIsDateLike = typeof lv === "string" || lv instanceof PyTimestamp;
|
|
1638
|
-
const reviewIsInt = typeof review_after === "number" && Number.isInteger(review_after) && typeof review_after !== "boolean";
|
|
1639
|
-
if (!lvIsDateLike || !reviewIsInt) {
|
|
1640
|
-
continue;
|
|
1641
|
-
}
|
|
1642
|
-
const lvStr = lv instanceof PyTimestamp ? lv.pyStr : lv;
|
|
1643
|
-
const parsed = _parseIsoDate(lvStr);
|
|
1644
|
-
if (parsed === null) {
|
|
1645
|
-
continue;
|
|
1646
|
-
}
|
|
1647
|
-
const due = _addDays(parsed, review_after);
|
|
1648
|
-
if (_dateDiffDays(due, today) < 0) {
|
|
1649
|
-
stale.push({
|
|
1650
|
-
file: p,
|
|
1651
|
-
type: mtype,
|
|
1652
|
-
id: entry["id"] ?? "?",
|
|
1653
|
-
overdue_days: _dateDiffDays(today, due)
|
|
1654
|
-
});
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
stale.sort((a, b) => b.overdue_days - a.overdue_days);
|
|
1658
|
-
return stale;
|
|
1659
|
-
}
|
|
1660
|
-
function _quarter_of(d) {
|
|
1661
|
-
let parsed = null;
|
|
1662
|
-
if (typeof d === "string") {
|
|
1663
|
-
const pd = _parseIsoDate(d.slice(0, 10));
|
|
1664
|
-
if (pd === null) {
|
|
1665
|
-
return "unknown";
|
|
1666
|
-
}
|
|
1667
|
-
parsed = pd;
|
|
1668
|
-
} else if (d instanceof PyTimestamp) {
|
|
1669
|
-
const pd = _parseIsoDate(d.pyStr.slice(0, 10));
|
|
1670
|
-
if (pd === null) {
|
|
1671
|
-
return "unknown";
|
|
1672
|
-
}
|
|
1673
|
-
parsed = pd;
|
|
1674
|
-
} else {
|
|
1675
|
-
return "unknown";
|
|
1676
|
-
}
|
|
1677
|
-
return `${parsed.y}Q${Math.floor((parsed.m - 1) / 3) + 1}`;
|
|
1678
|
-
}
|
|
1679
|
-
function _quarterly_stats() {
|
|
1680
|
-
const accepted = /* @__PURE__ */ new Map();
|
|
1681
|
-
for (const [, , entry] of _iter_curated_entries()) {
|
|
1682
|
-
const created = entry["created"] ?? entry["last_validated"];
|
|
1683
|
-
if (created !== void 0 && created !== null) {
|
|
1684
|
-
const q = _quarter_of(created);
|
|
1685
|
-
accepted.set(q, (accepted.get(q) ?? 0) + 1);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
const retired = /* @__PURE__ */ new Map();
|
|
1689
|
-
if (_isDir(INTAKE_ROOT)) {
|
|
1690
|
-
for (const jsonl of _globJsonl(INTAKE_ROOT)) {
|
|
1691
|
-
for (const obj of _readJsonl(jsonl)) {
|
|
1692
|
-
if (obj["type"] !== "supersede") {
|
|
1693
|
-
continue;
|
|
1694
|
-
}
|
|
1695
|
-
const ts = obj["ts"];
|
|
1696
|
-
if (typeof ts === "string") {
|
|
1697
|
-
const q = _quarter_of(ts);
|
|
1698
|
-
retired.set(q, (retired.get(q) ?? 0) + 1);
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
const total = _iter_curated_entries().length;
|
|
1704
|
-
const overdue = _staleness_report().length;
|
|
1705
|
-
const rate = total ? overdue / total : 0;
|
|
1706
|
-
return {
|
|
1707
|
-
accepted_by_quarter: _mapToObj(accepted),
|
|
1708
|
-
retired_by_quarter: _mapToObj(retired),
|
|
1709
|
-
staleness_rate: new PyFloat2(_pyRound3(rate)),
|
|
1710
|
-
curated_total: total,
|
|
1711
|
-
curated_overdue: overdue
|
|
1712
|
-
};
|
|
1713
|
-
}
|
|
1714
|
-
function _pyRound3(x) {
|
|
1715
|
-
const factor = 1e3;
|
|
1716
|
-
const scaled = x * factor;
|
|
1717
|
-
const floor = Math.floor(scaled);
|
|
1718
|
-
const diff = scaled - floor;
|
|
1719
|
-
let rounded;
|
|
1720
|
-
if (Math.abs(diff - 0.5) < 1e-9) {
|
|
1721
|
-
rounded = floor % 2 === 0 ? floor : floor + 1;
|
|
1722
|
-
} else {
|
|
1723
|
-
rounded = Math.round(scaled);
|
|
1724
|
-
}
|
|
1725
|
-
return rounded / factor;
|
|
1726
|
-
}
|
|
1727
|
-
function _role_mode_stats() {
|
|
1728
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1729
|
-
let files_scanned = 0;
|
|
1730
|
-
const unknown_modes = /* @__PURE__ */ new Set();
|
|
1731
|
-
for (const root of _MODE_SCAN_DIRS) {
|
|
1732
|
-
if (!_exists(root)) {
|
|
1733
|
-
continue;
|
|
1734
|
-
}
|
|
1735
|
-
for (const md of _rglobMd(root)) {
|
|
1736
|
-
let text;
|
|
1737
|
-
try {
|
|
1738
|
-
text = fs4.readFileSync(md, "utf-8");
|
|
1739
|
-
} catch {
|
|
1740
|
-
continue;
|
|
1741
|
-
}
|
|
1742
|
-
files_scanned += 1;
|
|
1743
|
-
_MODE_MARKER_PATTERN.lastIndex = 0;
|
|
1744
|
-
let m;
|
|
1745
|
-
while ((m = _MODE_MARKER_PATTERN.exec(text)) !== null) {
|
|
1746
|
-
const slug = m[1].toLowerCase();
|
|
1747
|
-
counts.set(slug, (counts.get(slug) ?? 0) + 1);
|
|
1748
|
-
if (!_KNOWN_MODES.has(slug)) {
|
|
1749
|
-
unknown_modes.add(slug);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
let total = 0;
|
|
1755
|
-
for (const v of counts.values()) {
|
|
1756
|
-
total += v;
|
|
1757
|
-
}
|
|
1758
|
-
return {
|
|
1759
|
-
total_markers: total,
|
|
1760
|
-
files_scanned,
|
|
1761
|
-
by_mode: _mapToObj(counts),
|
|
1762
|
-
unknown_modes: [...unknown_modes].sort()
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
function _exists(p) {
|
|
1766
|
-
try {
|
|
1767
|
-
fs4.statSync(p);
|
|
1768
|
-
return true;
|
|
1769
|
-
} catch {
|
|
1770
|
-
return false;
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
function _rglobMd(root) {
|
|
1774
|
-
const found = [];
|
|
1775
|
-
const walk = (dir) => {
|
|
1776
|
-
let entries;
|
|
1777
|
-
try {
|
|
1778
|
-
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
1779
|
-
} catch {
|
|
1780
|
-
return;
|
|
1781
|
-
}
|
|
1782
|
-
for (const ent of entries) {
|
|
1783
|
-
const full = path4.join(dir, ent.name);
|
|
1784
|
-
if (ent.isDirectory()) {
|
|
1785
|
-
walk(full);
|
|
1786
|
-
} else if (ent.isFile() && ent.name.endsWith(".md")) {
|
|
1787
|
-
found.push(full);
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
};
|
|
1791
|
-
walk(root);
|
|
1792
|
-
found.sort();
|
|
1793
|
-
return found;
|
|
1794
|
-
}
|
|
1795
|
-
function build_report() {
|
|
1796
|
-
const st = status();
|
|
1797
|
-
return {
|
|
1798
|
-
backend: {
|
|
1799
|
-
status: st.status,
|
|
1800
|
-
backend: st.backend,
|
|
1801
|
-
reason: st.reason
|
|
1802
|
-
},
|
|
1803
|
-
intake: _intake_stats(),
|
|
1804
|
-
staleness: _staleness_report(),
|
|
1805
|
-
quarterly: _quarterly_stats(),
|
|
1806
|
-
role_modes: _role_mode_stats()
|
|
1807
|
-
};
|
|
1808
|
-
}
|
|
1809
|
-
function _print_text(report2) {
|
|
1810
|
-
const out = [];
|
|
1811
|
-
const b = report2.backend;
|
|
1812
|
-
out.push(`Backend: ${b.status} (backend=${b.backend})`);
|
|
1813
|
-
if (b.reason) {
|
|
1814
|
-
out.push(` reason: ${b.reason}`);
|
|
1815
|
-
}
|
|
1816
|
-
const intake = report2.intake;
|
|
1817
|
-
out.push(`Intake: ${intake.total_active} active, ${intake.superseded} superseded`);
|
|
1818
|
-
for (const [t, n] of _sortedEntries(intake.by_type)) {
|
|
1819
|
-
out.push(` - ${t}: ${n}`);
|
|
1820
|
-
}
|
|
1821
|
-
const stale = report2.staleness;
|
|
1822
|
-
if (stale.length === 0) {
|
|
1823
|
-
out.push("Staleness: no curated entries past review_after_days");
|
|
1824
|
-
} else {
|
|
1825
|
-
out.push(`Staleness: ${stale.length} entrie(s) overdue`);
|
|
1826
|
-
for (const row of stale.slice(0, 5)) {
|
|
1827
|
-
out.push(` - ${row.id} (${row.type}) +${row.overdue_days}d ${row.file}`);
|
|
1828
|
-
}
|
|
1829
|
-
if (stale.length > 5) {
|
|
1830
|
-
out.push(` (+${stale.length - 5} more)`);
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
const q = report2.quarterly;
|
|
1834
|
-
out.push(`Quarterly: staleness-rate=${_pyPercent1(q.staleness_rate.value)} (${q.curated_overdue}/${q.curated_total})`);
|
|
1835
|
-
if (Object.keys(q.accepted_by_quarter).length > 0) {
|
|
1836
|
-
const acc = _sortedEntries(q.accepted_by_quarter).map(([k, v]) => `${k}:${v}`).join(", ");
|
|
1837
|
-
out.push(` accepted: ${acc}`);
|
|
1838
|
-
}
|
|
1839
|
-
if (Object.keys(q.retired_by_quarter).length > 0) {
|
|
1840
|
-
const ret = _sortedEntries(q.retired_by_quarter).map(([k, v]) => `${k}:${v}`).join(", ");
|
|
1841
|
-
out.push(` retired: ${ret}`);
|
|
1842
|
-
}
|
|
1843
|
-
const rm = report2.role_modes;
|
|
1844
|
-
if (rm.files_scanned) {
|
|
1845
|
-
out.push(`Role modes: ${rm.total_markers} marker(s) in ${rm.files_scanned} file(s)`);
|
|
1846
|
-
if (Object.keys(rm.by_mode).length > 0) {
|
|
1847
|
-
const modes = _sortedEntries(rm.by_mode).map(([k, v]) => `${k}:${v}`).join(", ");
|
|
1848
|
-
out.push(` by mode: ${modes}`);
|
|
1849
|
-
}
|
|
1850
|
-
if (rm.unknown_modes.length > 0) {
|
|
1851
|
-
out.push(` unknown: ${rm.unknown_modes.join(", ")} (not in the six reserved slugs)`);
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
process3.stdout.write(out.map((line) => `${line}
|
|
1855
|
-
`).join(""));
|
|
1856
|
-
}
|
|
1857
|
-
function _sortedEntries(obj) {
|
|
1858
|
-
return Object.entries(obj).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
1859
|
-
}
|
|
1860
|
-
function _pyPercent1(x) {
|
|
1861
|
-
return `${(x * 100).toFixed(1)}%`;
|
|
1862
|
-
}
|
|
1863
|
-
function pyJsonDumps(value, indent) {
|
|
1864
|
-
return _escapeNonAscii2(_dumpsIndent(value, indent, 0));
|
|
1865
|
-
}
|
|
1866
|
-
function _dumpsIndent(value, indent, depth) {
|
|
1867
|
-
const pad = " ".repeat(indent * (depth + 1));
|
|
1868
|
-
const closePad = " ".repeat(indent * depth);
|
|
1869
|
-
if (value === null || value === void 0) {
|
|
1870
|
-
return "null";
|
|
1871
|
-
}
|
|
1872
|
-
if (typeof value === "boolean") {
|
|
1873
|
-
return value ? "true" : "false";
|
|
1874
|
-
}
|
|
1875
|
-
if (typeof value === "number") {
|
|
1876
|
-
return _jsonNum(value);
|
|
1877
|
-
}
|
|
1878
|
-
if (typeof value === "string") {
|
|
1879
|
-
return _jsonStrAscii(value);
|
|
1880
|
-
}
|
|
1881
|
-
if (value instanceof PyTimestamp) {
|
|
1882
|
-
return _jsonStrAscii(value.pyStr);
|
|
1883
|
-
}
|
|
1884
|
-
if (value instanceof PyFloat2) {
|
|
1885
|
-
return _jsonFloat(value.value);
|
|
1886
|
-
}
|
|
1887
|
-
if (Array.isArray(value)) {
|
|
1888
|
-
if (value.length === 0) {
|
|
1889
|
-
return "[]";
|
|
1890
|
-
}
|
|
1891
|
-
const items = value.map((v) => pad + _dumpsIndent(v, indent, depth + 1));
|
|
1892
|
-
return `[
|
|
1893
|
-
${items.join(",\n")}
|
|
1894
|
-
${closePad}]`;
|
|
1895
|
-
}
|
|
1896
|
-
if (_isPlainObject2(value)) {
|
|
1897
|
-
const keys = Object.keys(value);
|
|
1898
|
-
if (keys.length === 0) {
|
|
1899
|
-
return "{}";
|
|
1900
|
-
}
|
|
1901
|
-
const items = keys.map((k) => `${pad}${_jsonStrAscii(k)}: ${_dumpsIndent(value[k], indent, depth + 1)}`);
|
|
1902
|
-
return `{
|
|
1903
|
-
${items.join(",\n")}
|
|
1904
|
-
${closePad}}`;
|
|
1905
|
-
}
|
|
1906
|
-
return _jsonStrAscii(String(value));
|
|
1907
|
-
}
|
|
1908
|
-
function _jsonNum(n) {
|
|
1909
|
-
if (!Number.isFinite(n)) {
|
|
1910
|
-
if (Number.isNaN(n)) {
|
|
1911
|
-
return "NaN";
|
|
1912
|
-
}
|
|
1913
|
-
return n > 0 ? "Infinity" : "-Infinity";
|
|
1914
|
-
}
|
|
1915
|
-
return String(n);
|
|
1916
|
-
}
|
|
1917
|
-
function _jsonFloat(n) {
|
|
1918
|
-
if (!Number.isFinite(n)) {
|
|
1919
|
-
if (Number.isNaN(n)) {
|
|
1920
|
-
return "NaN";
|
|
1921
|
-
}
|
|
1922
|
-
return n > 0 ? "Infinity" : "-Infinity";
|
|
1923
|
-
}
|
|
1924
|
-
return Number.isInteger(n) ? `${n}.0` : String(n);
|
|
1925
|
-
}
|
|
1926
|
-
function _jsonStrAscii(s) {
|
|
1927
|
-
let out = '"';
|
|
1928
|
-
for (const ch of s) {
|
|
1929
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
1930
|
-
switch (ch) {
|
|
1931
|
-
case '"':
|
|
1932
|
-
out += '\\"';
|
|
1933
|
-
break;
|
|
1934
|
-
case "\\":
|
|
1935
|
-
out += "\\\\";
|
|
1936
|
-
break;
|
|
1937
|
-
case "\n":
|
|
1938
|
-
out += "\\n";
|
|
1939
|
-
break;
|
|
1940
|
-
case "\r":
|
|
1941
|
-
out += "\\r";
|
|
1942
|
-
break;
|
|
1943
|
-
case " ":
|
|
1944
|
-
out += "\\t";
|
|
1945
|
-
break;
|
|
1946
|
-
case "\b":
|
|
1947
|
-
out += "\\b";
|
|
1948
|
-
break;
|
|
1949
|
-
case "\f":
|
|
1950
|
-
out += "\\f";
|
|
1951
|
-
break;
|
|
1952
|
-
default:
|
|
1953
|
-
if (code < 32) {
|
|
1954
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
1955
|
-
} else {
|
|
1956
|
-
out += ch;
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
return `${out}"`;
|
|
1961
|
-
}
|
|
1962
|
-
function _escapeNonAscii2(s) {
|
|
1963
|
-
let out = "";
|
|
1964
|
-
for (const ch of s) {
|
|
1965
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
1966
|
-
if (code > 127) {
|
|
1967
|
-
for (let i = 0; i < ch.length; i += 1) {
|
|
1968
|
-
out += `\\u${ch.charCodeAt(i).toString(16).padStart(4, "0")}`;
|
|
1969
|
-
}
|
|
1970
|
-
} else {
|
|
1971
|
-
out += ch;
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
return out;
|
|
1975
|
-
}
|
|
1976
|
-
var _PROG2 = "memory_report.py";
|
|
1977
|
-
var _USAGE2 = "usage: memory_report.py [-h] [--format {text,json}]\n";
|
|
1978
|
-
function _argError2(msg) {
|
|
1979
|
-
process3.stderr.write(_USAGE2);
|
|
1980
|
-
process3.stderr.write(`${_PROG2}: error: ${msg}
|
|
1981
|
-
`);
|
|
1982
|
-
process3.exit(2);
|
|
1983
|
-
}
|
|
1984
|
-
function _parseArgs2(argv) {
|
|
1985
|
-
const args = { format: "text" };
|
|
1986
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
1987
|
-
const a = argv[i];
|
|
1988
|
-
if (a === "--format") {
|
|
1989
|
-
args.format = _checkChoice(argv[++i], ["text", "json"], "--format");
|
|
1990
|
-
} else if (a.startsWith("--format=")) {
|
|
1991
|
-
args.format = _checkChoice(a.slice("--format=".length), ["text", "json"], "--format");
|
|
1992
|
-
} else if (a === "-h" || a === "--help") {
|
|
1993
|
-
process3.stdout.write(_USAGE2);
|
|
1994
|
-
process3.exit(0);
|
|
1995
|
-
} else {
|
|
1996
|
-
_argError2(`unrecognized arguments: ${a}`);
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
return args;
|
|
2000
|
-
}
|
|
2001
|
-
function _checkChoice(value, choices, flag) {
|
|
2002
|
-
if (value === void 0 || !choices.includes(value)) {
|
|
2003
|
-
_argError2(
|
|
2004
|
-
`argument ${flag}: invalid choice: '${value ?? ""}' (choose from ${choices.map((c) => `'${c}'`).join(", ")})`
|
|
2005
|
-
);
|
|
2006
|
-
}
|
|
2007
|
-
return value;
|
|
2008
|
-
}
|
|
2009
|
-
function main3() {
|
|
2010
|
-
const args = _parseArgs2(process3.argv.slice(2));
|
|
2011
|
-
const report2 = build_report();
|
|
2012
|
-
if (args.format === "json") {
|
|
2013
|
-
process3.stdout.write(`${pyJsonDumps(report2, 2)}
|
|
2014
|
-
`);
|
|
2015
|
-
} else {
|
|
2016
|
-
_print_text(report2);
|
|
2017
|
-
}
|
|
2018
|
-
return 0;
|
|
2019
|
-
}
|
|
2020
|
-
function _isCliEntry3() {
|
|
2021
|
-
if (true) {
|
|
2022
|
-
return false;
|
|
2023
|
-
}
|
|
2024
|
-
if (process3.argv[1] === void 0) {
|
|
2025
|
-
return false;
|
|
2026
|
-
}
|
|
2027
|
-
const argvUrl = pathToFileURL3(path4.resolve(process3.argv[1])).href;
|
|
2028
|
-
if (import.meta.url === argvUrl) {
|
|
2029
|
-
return true;
|
|
2030
|
-
}
|
|
2031
|
-
try {
|
|
2032
|
-
const here = fs4.realpathSync(fileURLToPath4(import.meta.url));
|
|
2033
|
-
const argv = fs4.realpathSync(path4.resolve(process3.argv[1]));
|
|
2034
|
-
return here === argv;
|
|
2035
|
-
} catch {
|
|
2036
|
-
return false;
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
var _isMain2 = _isCliEntry3();
|
|
2040
|
-
if (_isMain2) {
|
|
2041
|
-
process3.exit(main3());
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
// src/scripts/_lib/memory_fts_index.ts
|
|
2045
|
-
var MEMORY_INDEX_REL = path5.join("agents", "runtime", "state", "memory-index-v1.sqlite3");
|
|
2046
|
-
var SCHEMA_VERSION = 1;
|
|
2047
|
-
var CURATED_STATUS_EXCLUDE = /* @__PURE__ */ new Set(["deprecated", "archived", "superseded"]);
|
|
2048
|
-
var INDEX_TEXT_FIELDS = ["path", "key", "symptom", "feature", "rule", "body"];
|
|
2049
|
-
function resolveIndexPath(consumerRoot) {
|
|
2050
|
-
return path5.join(consumerRoot ?? process.cwd(), MEMORY_INDEX_REL);
|
|
2051
|
-
}
|
|
2052
|
-
function _pyStr(v) {
|
|
2053
|
-
if (v === void 0 || v === null) return "";
|
|
2054
|
-
if (typeof v === "string") return v;
|
|
2055
|
-
return String(v);
|
|
2056
|
-
}
|
|
2057
|
-
function _entryIndexText(entry) {
|
|
2058
|
-
const parts = [];
|
|
2059
|
-
for (const f of INDEX_TEXT_FIELDS) {
|
|
2060
|
-
const v = entry[f];
|
|
2061
|
-
if (typeof v === "string") parts.push(v);
|
|
2062
|
-
else if (Array.isArray(v)) for (const x of v) parts.push(_pyStr(x));
|
|
2063
|
-
}
|
|
2064
|
-
return parts.join(" | ");
|
|
2065
|
-
}
|
|
2066
|
-
function _isPlainObject3(v) {
|
|
2067
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2068
|
-
}
|
|
2069
|
-
function _collectCurated(memoryRoot) {
|
|
2070
|
-
const out = [];
|
|
2071
|
-
const previous = MEMORY_ROOT;
|
|
2072
|
-
_setMemoryRoot(memoryRoot);
|
|
2073
|
-
try {
|
|
2074
|
-
for (const [file, mtype, entry] of _iter_curated_entries()) {
|
|
2075
|
-
const status2 = entry["status"];
|
|
2076
|
-
if (typeof status2 === "string" && CURATED_STATUS_EXCLUDE.has(status2)) continue;
|
|
2077
|
-
const id = typeof entry["id"] === "string" ? entry["id"] : "";
|
|
2078
|
-
if (id === "") continue;
|
|
2079
|
-
out.push({ id, mtype, source: "curated", path: file, text: _entryIndexText(entry) });
|
|
2080
|
-
}
|
|
2081
|
-
} finally {
|
|
2082
|
-
_setMemoryRoot(previous);
|
|
2083
|
-
}
|
|
2084
|
-
return out;
|
|
2085
|
-
}
|
|
2086
|
-
function _globJsonl2(intakeRoot) {
|
|
2087
|
-
let names;
|
|
2088
|
-
try {
|
|
2089
|
-
names = fs5.readdirSync(intakeRoot);
|
|
2090
|
-
} catch {
|
|
2091
|
-
return [];
|
|
2092
|
-
}
|
|
2093
|
-
return names.filter((n) => n.endsWith(".jsonl")).sort().map((n) => path5.join(intakeRoot, n));
|
|
2094
|
-
}
|
|
2095
|
-
function _collectIntake(intakeRoot) {
|
|
2096
|
-
const out = [];
|
|
2097
|
-
for (const jsonl of _globJsonl2(intakeRoot)) {
|
|
2098
|
-
const by_id = /* @__PURE__ */ new Map();
|
|
2099
|
-
const superseded = /* @__PURE__ */ new Set();
|
|
2100
|
-
let content;
|
|
2101
|
-
try {
|
|
2102
|
-
content = fs5.readFileSync(jsonl, "utf-8");
|
|
2103
|
-
} catch {
|
|
2104
|
-
continue;
|
|
2105
|
-
}
|
|
2106
|
-
for (let line of content.split(/\n/)) {
|
|
2107
|
-
line = line.trim();
|
|
2108
|
-
if (!line) continue;
|
|
2109
|
-
let obj;
|
|
2110
|
-
try {
|
|
2111
|
-
obj = JSON.parse(line);
|
|
2112
|
-
} catch {
|
|
2113
|
-
continue;
|
|
2114
|
-
}
|
|
2115
|
-
if (!_isPlainObject3(obj)) continue;
|
|
2116
|
-
if (obj["type"] === "supersede") {
|
|
2117
|
-
const target = obj["supersedes"];
|
|
2118
|
-
if (typeof target === "string") superseded.add(target);
|
|
2119
|
-
continue;
|
|
2120
|
-
}
|
|
2121
|
-
const eid = obj["id"];
|
|
2122
|
-
if (typeof eid === "string") by_id.set(eid, obj);
|
|
2123
|
-
}
|
|
2124
|
-
for (const [eid, obj] of by_id.entries()) {
|
|
2125
|
-
if (superseded.has(eid)) continue;
|
|
2126
|
-
const mtype = typeof obj["entry_type"] === "string" ? obj["entry_type"] : "";
|
|
2127
|
-
out.push({ id: eid, mtype, source: "intake", path: jsonl, text: _entryIndexText(obj) });
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
return out;
|
|
2131
|
-
}
|
|
2132
|
-
function collectEntries(memoryRoot, intakeRoot) {
|
|
2133
|
-
return [..._collectCurated(memoryRoot), ..._collectIntake(intakeRoot)];
|
|
2134
|
-
}
|
|
2135
|
-
function _sourcesMaxMtimeMs(memoryRoot, intakeRoot) {
|
|
2136
|
-
let max = 0;
|
|
2137
|
-
const bump = (p) => {
|
|
2138
|
-
try {
|
|
2139
|
-
const st = fs5.statSync(p);
|
|
2140
|
-
if (st.mtimeMs > max) max = st.mtimeMs;
|
|
2141
|
-
} catch {
|
|
2142
|
-
}
|
|
2143
|
-
};
|
|
2144
|
-
const walk = (dir) => {
|
|
2145
|
-
let entries;
|
|
2146
|
-
try {
|
|
2147
|
-
entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
2148
|
-
} catch {
|
|
2149
|
-
return;
|
|
2150
|
-
}
|
|
2151
|
-
for (const e of entries) {
|
|
2152
|
-
const full = path5.join(dir, e.name);
|
|
2153
|
-
if (e.isDirectory()) walk(full);
|
|
2154
|
-
else if (e.isFile() && e.name.endsWith(".yml")) bump(full);
|
|
2155
|
-
}
|
|
2156
|
-
};
|
|
2157
|
-
let memEntries;
|
|
2158
|
-
try {
|
|
2159
|
-
memEntries = fs5.readdirSync(memoryRoot, { withFileTypes: true });
|
|
2160
|
-
for (const e of memEntries) {
|
|
2161
|
-
const full = path5.join(memoryRoot, e.name);
|
|
2162
|
-
if (e.isFile() && e.name.endsWith(".yml")) bump(full);
|
|
2163
|
-
else if (e.isDirectory()) walk(full);
|
|
2164
|
-
}
|
|
2165
|
-
} catch {
|
|
2166
|
-
}
|
|
2167
|
-
for (const f of _globJsonl2(intakeRoot)) bump(f);
|
|
2168
|
-
return max;
|
|
2169
|
-
}
|
|
2170
|
-
var META_KEY_SOURCES_MTIME = "sources_mtime_ms";
|
|
2171
|
-
function isStale(indexPath, memoryRoot, intakeRoot) {
|
|
2172
|
-
if (!fs5.existsSync(indexPath)) return true;
|
|
2173
|
-
let sqlite;
|
|
2174
|
-
try {
|
|
2175
|
-
sqlite = loadSqliteSync("memory_fts_index.isStale");
|
|
2176
|
-
} catch {
|
|
2177
|
-
return true;
|
|
2178
|
-
}
|
|
2179
|
-
let conn = null;
|
|
2180
|
-
try {
|
|
2181
|
-
conn = new sqlite.DatabaseSync(indexPath, { readOnly: true });
|
|
2182
|
-
if (readUserVersion(conn) !== SCHEMA_VERSION) return true;
|
|
2183
|
-
const row = conn.prepare("SELECT value FROM _meta WHERE key = ?").get(META_KEY_SOURCES_MTIME);
|
|
2184
|
-
const stored = row?.["value"];
|
|
2185
|
-
const storedMs = typeof stored === "string" ? Number(stored) : NaN;
|
|
2186
|
-
if (!Number.isFinite(storedMs)) return true;
|
|
2187
|
-
return _sourcesMaxMtimeMs(memoryRoot, intakeRoot) > storedMs;
|
|
2188
|
-
} catch {
|
|
2189
|
-
return true;
|
|
2190
|
-
} finally {
|
|
2191
|
-
try {
|
|
2192
|
-
conn?.close();
|
|
2193
|
-
} catch {
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
function buildIndex(indexPath, memoryRoot, intakeRoot) {
|
|
2198
|
-
fs5.mkdirSync(path5.dirname(indexPath), { recursive: true });
|
|
2199
|
-
try {
|
|
2200
|
-
fs5.rmSync(indexPath, { force: true });
|
|
2201
|
-
fs5.rmSync(`${indexPath}-wal`, { force: true });
|
|
2202
|
-
fs5.rmSync(`${indexPath}-shm`, { force: true });
|
|
2203
|
-
} catch {
|
|
2204
|
-
}
|
|
2205
|
-
const { DatabaseSync } = loadSqliteSync("memory_fts_index.buildIndex");
|
|
2206
|
-
const conn = new DatabaseSync(indexPath);
|
|
2207
|
-
let count = 0;
|
|
2208
|
-
try {
|
|
2209
|
-
conn.exec("PRAGMA journal_mode=WAL");
|
|
2210
|
-
conn.exec(
|
|
2211
|
-
"CREATE VIRTUAL TABLE entries USING fts5(id UNINDEXED, mtype UNINDEXED, source UNINDEXED, path UNINDEXED, text)"
|
|
2212
|
-
);
|
|
2213
|
-
conn.exec("CREATE TABLE _meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
2214
|
-
const stmt = conn.prepare(
|
|
2215
|
-
"INSERT INTO entries (id, mtype, source, path, text) VALUES (?, ?, ?, ?, ?)"
|
|
2216
|
-
);
|
|
2217
|
-
for (const e of collectEntries(memoryRoot, intakeRoot)) {
|
|
2218
|
-
stmt.run(e.id, e.mtype, e.source, e.path, e.text);
|
|
2219
|
-
count += 1;
|
|
2220
|
-
}
|
|
2221
|
-
const metaStmt = conn.prepare("INSERT INTO _meta (key, value) VALUES (?, ?)");
|
|
2222
|
-
metaStmt.run(META_KEY_SOURCES_MTIME, String(_sourcesMaxMtimeMs(memoryRoot, intakeRoot)));
|
|
2223
|
-
stampUserVersion(conn, SCHEMA_VERSION);
|
|
2224
|
-
conn.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
2225
|
-
conn.exec("PRAGMA journal_mode=DELETE");
|
|
2226
|
-
} finally {
|
|
2227
|
-
conn.close();
|
|
2228
|
-
}
|
|
2229
|
-
return { indexPath, entriesIndexed: count };
|
|
2230
|
-
}
|
|
2231
|
-
function ensureIndex(indexPath, memoryRoot, intakeRoot) {
|
|
2232
|
-
if (!probeFts5Sync()) return false;
|
|
2233
|
-
try {
|
|
2234
|
-
if (isStale(indexPath, memoryRoot, intakeRoot)) {
|
|
2235
|
-
buildIndex(indexPath, memoryRoot, intakeRoot);
|
|
2236
|
-
}
|
|
2237
|
-
return true;
|
|
2238
|
-
} catch {
|
|
2239
|
-
return false;
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
function _sanitizeFtsClause(key) {
|
|
2243
|
-
return key.replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
2244
|
-
}
|
|
2245
|
-
function queryIndex(indexPath, keys, types) {
|
|
2246
|
-
const clauses = keys.map(_sanitizeFtsClause).filter((c) => c.length > 0);
|
|
2247
|
-
if (clauses.length === 0) return [];
|
|
2248
|
-
let sqlite;
|
|
2249
|
-
try {
|
|
2250
|
-
sqlite = loadSqliteSync("memory_fts_index.queryIndex");
|
|
2251
|
-
} catch {
|
|
2252
|
-
return [];
|
|
2253
|
-
}
|
|
2254
|
-
let conn = null;
|
|
2255
|
-
try {
|
|
2256
|
-
conn = new sqlite.DatabaseSync(indexPath, { readOnly: true });
|
|
2257
|
-
const matchQuery = clauses.map((c) => `(${c})`).join(" OR ");
|
|
2258
|
-
const typeFilter = types.length > 0 ? `AND mtype IN (${types.map(() => "?").join(",")})` : "";
|
|
2259
|
-
const rows = conn.prepare(
|
|
2260
|
-
`SELECT id, mtype, source, path, bm25(entries) AS score
|
|
2261
|
-
FROM entries
|
|
2262
|
-
WHERE entries MATCH ? ${typeFilter}
|
|
2263
|
-
ORDER BY score ASC`
|
|
2264
|
-
).all(matchQuery, ...types);
|
|
2265
|
-
return rows.map((r) => {
|
|
2266
|
-
const bm25 = typeof r["score"] === "number" ? r["score"] : Number(r["score"] ?? 0);
|
|
2267
|
-
return {
|
|
2268
|
-
id: String(r["id"]),
|
|
2269
|
-
mtype: String(r["mtype"]),
|
|
2270
|
-
source: r["source"] ?? "curated",
|
|
2271
|
-
path: String(r["path"]),
|
|
2272
|
-
relevance: Math.max(0, -bm25)
|
|
2273
|
-
};
|
|
2274
|
-
});
|
|
2275
|
-
} catch {
|
|
2276
|
-
return [];
|
|
2277
|
-
} finally {
|
|
2278
|
-
try {
|
|
2279
|
-
conn?.close();
|
|
2280
|
-
} catch {
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
// src/scripts/learning_sidecar.ts
|
|
2286
|
-
import { createHash } from "node:crypto";
|
|
2287
|
-
import * as fs6 from "node:fs";
|
|
2288
|
-
import * as path6 from "node:path";
|
|
2289
|
-
import process4 from "node:process";
|
|
2290
|
-
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
2291
|
-
var PROG = "learning_sidecar.ts";
|
|
2292
|
-
var HALF_LIFE_DAYS = 30;
|
|
2293
|
-
var MIN_CORROBORATIONS = 2;
|
|
2294
|
-
var SIDECAR_NAME = ".agent-learning.json";
|
|
2295
|
-
var LESSONS_NAME = "LESSONS.md";
|
|
2296
|
-
var SCHEMA_VERSION2 = 1;
|
|
2297
|
-
function _parseTsMs(ts) {
|
|
2298
|
-
const t = Date.parse(ts);
|
|
2299
|
-
return Number.isNaN(t) ? null : t;
|
|
2300
|
-
}
|
|
2301
|
-
function _bodyHash(body) {
|
|
2302
|
-
return createHash("sha256").update(body.trim().toLowerCase()).digest("hex").slice(0, 12);
|
|
2303
|
-
}
|
|
2304
|
-
function _globSignalFiles(dir) {
|
|
2305
|
-
let names;
|
|
2306
|
-
try {
|
|
2307
|
-
names = fs6.readdirSync(dir);
|
|
2308
|
-
} catch {
|
|
2309
|
-
return [];
|
|
2310
|
-
}
|
|
2311
|
-
return names.filter((n) => n.startsWith("signals-") && n.endsWith(".jsonl")).sort().map((n) => path6.join(dir, n));
|
|
2312
|
-
}
|
|
2313
|
-
function readSignals(intakeDir) {
|
|
2314
|
-
const out = [];
|
|
2315
|
-
for (const file of _globSignalFiles(intakeDir)) {
|
|
2316
|
-
let content;
|
|
2317
|
-
try {
|
|
2318
|
-
content = fs6.readFileSync(file, "utf-8");
|
|
2319
|
-
} catch {
|
|
2320
|
-
continue;
|
|
2321
|
-
}
|
|
2322
|
-
for (const raw of content.split("\n")) {
|
|
2323
|
-
const line = raw.trim();
|
|
2324
|
-
if (!line) continue;
|
|
2325
|
-
let obj;
|
|
2326
|
-
try {
|
|
2327
|
-
obj = JSON.parse(line);
|
|
2328
|
-
} catch {
|
|
2329
|
-
continue;
|
|
2330
|
-
}
|
|
2331
|
-
if (typeof obj !== "object" || obj === null) continue;
|
|
2332
|
-
const r = obj;
|
|
2333
|
-
const ts = typeof r["ts"] === "string" ? r["ts"] : "";
|
|
2334
|
-
const tsMs = _parseTsMs(ts);
|
|
2335
|
-
const entryType = typeof r["entry_type"] === "string" ? r["entry_type"] : "";
|
|
2336
|
-
const p = typeof r["path"] === "string" ? r["path"] : "";
|
|
2337
|
-
const body = typeof r["body"] === "string" ? r["body"] : "";
|
|
2338
|
-
if (tsMs === null || !entryType || !p || !body) continue;
|
|
2339
|
-
const pol = r["polarity"] === "dead_end" ? "dead_end" : "preferred";
|
|
2340
|
-
out.push({
|
|
2341
|
-
id: typeof r["id"] === "string" ? r["id"] : "",
|
|
2342
|
-
ts,
|
|
2343
|
-
tsMs,
|
|
2344
|
-
origin: typeof r["origin"] === "string" ? r["origin"] : "unknown",
|
|
2345
|
-
entryType,
|
|
2346
|
-
path: p,
|
|
2347
|
-
body,
|
|
2348
|
-
polarity: pol
|
|
2349
|
-
});
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
return out;
|
|
2353
|
-
}
|
|
2354
|
-
function aggregate(signals, nowMs) {
|
|
2355
|
-
const halfLifeMs = HALF_LIFE_DAYS * 24 * 60 * 60 * 1e3;
|
|
2356
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2357
|
-
for (const s of signals) {
|
|
2358
|
-
const gk = `${s.entryType}\0${s.path}`;
|
|
2359
|
-
let g = groups.get(gk);
|
|
2360
|
-
if (g === void 0) {
|
|
2361
|
-
g = { entryType: s.entryType, path: s.path, claims: /* @__PURE__ */ new Map() };
|
|
2362
|
-
groups.set(gk, g);
|
|
2363
|
-
}
|
|
2364
|
-
const ck = _bodyHash(s.body);
|
|
2365
|
-
let c = g.claims.get(ck);
|
|
2366
|
-
if (c === void 0) {
|
|
2367
|
-
c = { body: s.body, polarity: s.polarity, origins: /* @__PURE__ */ new Set(), score: 0, lastMs: -1, lastTs: "" };
|
|
2368
|
-
g.claims.set(ck, c);
|
|
2369
|
-
}
|
|
2370
|
-
const ageMs = Math.max(0, nowMs - s.tsMs);
|
|
2371
|
-
c.score += Math.pow(0.5, ageMs / halfLifeMs);
|
|
2372
|
-
c.origins.add(s.origin);
|
|
2373
|
-
if (s.tsMs > c.lastMs) {
|
|
2374
|
-
c.lastMs = s.tsMs;
|
|
2375
|
-
c.lastTs = s.ts;
|
|
2376
|
-
c.polarity = s.polarity;
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
const lessons = [];
|
|
2380
|
-
for (const g of groups.values()) {
|
|
2381
|
-
const claims = [...g.claims.values()].sort(
|
|
2382
|
-
(a, b) => b.score - a.score || b.lastMs - a.lastMs || (a.body < b.body ? -1 : 1)
|
|
2383
|
-
);
|
|
2384
|
-
const top = claims[0];
|
|
2385
|
-
if (top === void 0) continue;
|
|
2386
|
-
const corroborations = top.origins.size;
|
|
2387
|
-
if (corroborations < MIN_CORROBORATIONS) continue;
|
|
2388
|
-
const secondCorroborated = claims.slice(1).some((c) => c.origins.size >= MIN_CORROBORATIONS);
|
|
2389
|
-
let verdict;
|
|
2390
|
-
if (secondCorroborated) verdict = "contested";
|
|
2391
|
-
else if (top.polarity === "dead_end") verdict = "dead_end";
|
|
2392
|
-
else verdict = "preferred";
|
|
2393
|
-
lessons.push({
|
|
2394
|
-
entry_type: g.entryType,
|
|
2395
|
-
path: g.path,
|
|
2396
|
-
verdict,
|
|
2397
|
-
score: Math.round(top.score * 1e3) / 1e3,
|
|
2398
|
-
corroborations,
|
|
2399
|
-
last_ts: top.lastTs,
|
|
2400
|
-
body: top.body
|
|
2401
|
-
});
|
|
2402
|
-
}
|
|
2403
|
-
lessons.sort(
|
|
2404
|
-
(a, b) => a.entry_type < b.entry_type ? -1 : a.entry_type > b.entry_type ? 1 : a.path < b.path ? -1 : a.path > b.path ? 1 : 0
|
|
2405
|
-
);
|
|
2406
|
-
return lessons;
|
|
2407
|
-
}
|
|
2408
|
-
function buildSidecar(intakeDir, nowIso) {
|
|
2409
|
-
const nowMs = _parseTsMs(nowIso) ?? 0;
|
|
2410
|
-
const lessons = aggregate(readSignals(intakeDir), nowMs);
|
|
2411
|
-
return { schema_version: SCHEMA_VERSION2, generated_at: nowIso, lessons };
|
|
2412
|
-
}
|
|
2413
|
-
function renderLessonsMd(sidecar) {
|
|
2414
|
-
const dead = sidecar.lessons.filter((l) => l.verdict === "dead_end");
|
|
2415
|
-
const contested = sidecar.lessons.filter((l) => l.verdict === "contested");
|
|
2416
|
-
const L = [];
|
|
2417
|
-
L.push("# Lessons (generated \u2014 do not edit)");
|
|
2418
|
-
L.push("");
|
|
2419
|
-
L.push(`> Regenerated from the intake signal log by \`${PROG}\`. Local-only,`);
|
|
2420
|
-
L.push("> intake-derived; never a substitute for curated memory. Proven lessons");
|
|
2421
|
-
L.push("> should be MANUALLY promoted into `agents/memory/<type>/*.yml`.");
|
|
2422
|
-
L.push("");
|
|
2423
|
-
L.push("## Known dead ends \u2014 don't re-derive");
|
|
2424
|
-
L.push("");
|
|
2425
|
-
if (dead.length === 0) {
|
|
2426
|
-
L.push("_None corroborated yet._");
|
|
2427
|
-
} else {
|
|
2428
|
-
for (const l of dead) L.push(`- \`${l.entry_type}\` @ \`${l.path}\` \u2014 ${l.body}`);
|
|
2429
|
-
}
|
|
2430
|
-
L.push("");
|
|
2431
|
-
L.push("## Contested \u2014 recency-resolved, verify before relying");
|
|
2432
|
-
L.push("");
|
|
2433
|
-
if (contested.length === 0) {
|
|
2434
|
-
L.push("_None._");
|
|
2435
|
-
} else {
|
|
2436
|
-
for (const l of contested) L.push(`- \`${l.entry_type}\` @ \`${l.path}\` \u2014 ${l.body}`);
|
|
2437
|
-
}
|
|
2438
|
-
L.push("");
|
|
2439
|
-
return L.join("\n");
|
|
2440
|
-
}
|
|
2441
|
-
function main4(argv) {
|
|
2442
|
-
let intakeDir = path6.join("agents", "memory", "intake");
|
|
2443
|
-
let outDir = path6.join("agents", "memory");
|
|
2444
|
-
let nowIso = "";
|
|
2445
|
-
let write = false;
|
|
2446
|
-
let format = "text";
|
|
2447
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2448
|
-
const a = argv[i];
|
|
2449
|
-
if (a === "--intake-dir") intakeDir = argv[++i] ?? intakeDir;
|
|
2450
|
-
else if (a === "--out-dir") outDir = argv[++i] ?? outDir;
|
|
2451
|
-
else if (a === "--now") nowIso = argv[++i] ?? "";
|
|
2452
|
-
else if (a === "--write") write = true;
|
|
2453
|
-
else if (a === "--format") format = argv[++i] ?? "text";
|
|
2454
|
-
else if (a === "-h" || a === "--help") {
|
|
2455
|
-
process4.stdout.write(
|
|
2456
|
-
`usage: ${PROG} [--intake-dir DIR] [--out-dir DIR] [--now ISO] [--write] [--format text|json]
|
|
2457
|
-
`
|
|
2458
|
-
);
|
|
2459
|
-
return 0;
|
|
2460
|
-
} else {
|
|
2461
|
-
process4.stderr.write(`${PROG}: error: unknown argument ${a}
|
|
2462
|
-
`);
|
|
2463
|
-
return 2;
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
if (!nowIso) {
|
|
2467
|
-
nowIso = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
2468
|
-
}
|
|
2469
|
-
let sidecar;
|
|
2470
|
-
try {
|
|
2471
|
-
sidecar = buildSidecar(intakeDir, nowIso);
|
|
2472
|
-
} catch (exc) {
|
|
2473
|
-
process4.stderr.write(`${PROG}: internal error: ${String(exc)}
|
|
2474
|
-
`);
|
|
2475
|
-
return 3;
|
|
2476
|
-
}
|
|
2477
|
-
if (write) {
|
|
2478
|
-
fs6.mkdirSync(outDir, { recursive: true });
|
|
2479
|
-
fs6.writeFileSync(path6.join(outDir, SIDECAR_NAME), JSON.stringify(sidecar, null, 2) + "\n");
|
|
2480
|
-
fs6.writeFileSync(path6.join(outDir, LESSONS_NAME), renderLessonsMd(sidecar));
|
|
2481
|
-
}
|
|
2482
|
-
if (format === "json") {
|
|
2483
|
-
process4.stdout.write(JSON.stringify(sidecar, null, 2) + "\n");
|
|
2484
|
-
return 0;
|
|
2485
|
-
}
|
|
2486
|
-
const byVerdict = { preferred: 0, contested: 0, dead_end: 0 };
|
|
2487
|
-
for (const l of sidecar.lessons) byVerdict[l.verdict] += 1;
|
|
2488
|
-
process4.stdout.write(
|
|
2489
|
-
`${PROG}: ${sidecar.lessons.length} lesson(s) \u2014 ${byVerdict.preferred} preferred, ${byVerdict.contested} contested, ${byVerdict.dead_end} dead-end${write ? " (written)" : ""}
|
|
2490
|
-
`
|
|
2491
|
-
);
|
|
2492
|
-
return 0;
|
|
2493
|
-
}
|
|
2494
|
-
var _bundled = true;
|
|
2495
|
-
if (!_bundled && fileURLToPath5(import.meta.url) === path6.resolve(process4.argv[1] ?? "")) {
|
|
2496
|
-
process4.exit(main4(process4.argv.slice(2)));
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
// src/scripts/memory_lookup.ts
|
|
2500
|
-
var PYYAML_DATE_ONLY_RE2 = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
|
2501
|
-
var PYYAML_TIMESTAMP_RE2 = /^(?:[0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}(?:[Tt]|[ \t]+)[0-9]{1,2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]*)?(?:[ \t]*(?:Z|[-+][0-9]{1,2}(?::[0-9]{2})?))?)$/;
|
|
2502
|
-
var PyTimestamp2 = class {
|
|
2503
|
-
constructor(pyStr) {
|
|
2504
|
-
this.pyStr = pyStr;
|
|
2505
|
-
}
|
|
2506
|
-
pyStr;
|
|
2507
|
-
toString() {
|
|
2508
|
-
return this.pyStr;
|
|
2509
|
-
}
|
|
2510
|
-
};
|
|
2511
|
-
var PyFloat3 = class {
|
|
2512
|
-
constructor(value) {
|
|
2513
|
-
this.value = value;
|
|
2514
|
-
}
|
|
2515
|
-
value;
|
|
2516
|
-
};
|
|
2517
|
-
var MEMORY_ROOT2 = path7.join("agents", "memory");
|
|
2518
|
-
var INTAKE_ROOT2 = path7.join(MEMORY_ROOT2, "intake");
|
|
2519
|
-
var KNOWLEDGE_ROOT = path7.join(MEMORY_ROOT2, "knowledge");
|
|
2520
|
-
var _LEX_TYPE_FILES_MAX = 200;
|
|
2521
|
-
var _LEX_CORPUS_FILES_MAX = 500;
|
|
2522
|
-
var _LEX_SOURCE_FACTOR = { curated: 1, intake: 0.9, knowledge: 0.85 };
|
|
2523
|
-
var _lexTripwireOverride = null;
|
|
2524
|
-
var _corpusFileCounts = null;
|
|
2525
|
-
var _learningOverlay;
|
|
2526
|
-
function _loadLearningOverlay() {
|
|
2527
|
-
if (_learningOverlay !== void 0) return _learningOverlay;
|
|
2528
|
-
const file = path7.join(MEMORY_ROOT2, SIDECAR_NAME);
|
|
2529
|
-
try {
|
|
2530
|
-
const parsed = JSON.parse(fs7.readFileSync(file, "utf-8"));
|
|
2531
|
-
const lessons = _isPlainObject4(parsed) ? parsed["lessons"] : null;
|
|
2532
|
-
if (!Array.isArray(lessons)) {
|
|
2533
|
-
_learningOverlay = null;
|
|
2534
|
-
return null;
|
|
2535
|
-
}
|
|
2536
|
-
const map = /* @__PURE__ */ new Map();
|
|
2537
|
-
for (const l of lessons) {
|
|
2538
|
-
if (!_isPlainObject4(l)) continue;
|
|
2539
|
-
const t = l["entry_type"];
|
|
2540
|
-
const p = l["path"];
|
|
2541
|
-
const v = l["verdict"];
|
|
2542
|
-
if (typeof t === "string" && typeof p === "string" && (v === "preferred" || v === "contested" || v === "dead_end")) {
|
|
2543
|
-
map.set(`${t}\0${p}`, v);
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
_learningOverlay = map.size > 0 ? map : null;
|
|
2547
|
-
} catch {
|
|
2548
|
-
_learningOverlay = null;
|
|
2549
|
-
}
|
|
2550
|
-
return _learningOverlay;
|
|
2551
|
-
}
|
|
2552
|
-
function _learningVerdictFor(mtype, entry) {
|
|
2553
|
-
const overlay = _loadLearningOverlay();
|
|
2554
|
-
if (overlay === null) return void 0;
|
|
2555
|
-
const anchor = typeof entry["path"] === "string" ? entry["path"] : entry["key"];
|
|
2556
|
-
if (typeof anchor !== "string" || anchor === "") return void 0;
|
|
2557
|
-
return overlay.get(`${mtype}\0${anchor}`);
|
|
2558
|
-
}
|
|
2559
|
-
var CURATED_TYPES2 = /* @__PURE__ */ new Set([
|
|
2560
|
-
"ownership",
|
|
2561
|
-
"historical-patterns",
|
|
2562
|
-
"domain-invariants",
|
|
2563
|
-
"incident-learnings",
|
|
2564
|
-
"product-rules"
|
|
2565
|
-
]);
|
|
2566
|
-
var KNOWLEDGE_TYPE = "knowledge";
|
|
2567
|
-
var CROSS_REPO_TYPE = "cross-repo";
|
|
2568
|
-
var _CURATED_STATUS_EXCLUDE = /* @__PURE__ */ new Set(["deprecated", "archived", "superseded"]);
|
|
2569
|
-
function _statusExcluded(entry) {
|
|
2570
|
-
const status2 = entry["status"];
|
|
2571
|
-
return typeof status2 === "string" && _CURATED_STATUS_EXCLUDE.has(status2);
|
|
2572
|
-
}
|
|
2573
|
-
var Hit = class {
|
|
2574
|
-
id;
|
|
2575
|
-
type;
|
|
2576
|
-
source;
|
|
2577
|
-
// HitSource, kept as string to mirror the Python dataclass
|
|
2578
|
-
path;
|
|
2579
|
-
// file (or logical locator) that produced the hit
|
|
2580
|
-
score;
|
|
2581
|
-
// naive, content-match based [0..1]
|
|
2582
|
-
entry;
|
|
2583
|
-
constructor(id, type, source, p, score, entry = {}) {
|
|
2584
|
-
this.id = id;
|
|
2585
|
-
this.type = type;
|
|
2586
|
-
this.source = source;
|
|
2587
|
-
this.path = p;
|
|
2588
|
-
this.score = score;
|
|
2589
|
-
this.entry = entry;
|
|
2590
|
-
}
|
|
2591
|
-
as_dict() {
|
|
2592
|
-
return {
|
|
2593
|
-
id: this.id,
|
|
2594
|
-
type: this.type,
|
|
2595
|
-
source: this.source,
|
|
2596
|
-
path: this.path,
|
|
2597
|
-
// score is a Python float — render `0.0` not `0` for whole numbers.
|
|
2598
|
-
score: new PyFloat3(this.score),
|
|
2599
|
-
entry: this.entry
|
|
2600
|
-
};
|
|
2601
|
-
}
|
|
2602
|
-
};
|
|
2603
|
-
function _isPlainObject4(v) {
|
|
2604
|
-
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof PyTimestamp2) && !(v instanceof PyFloat3);
|
|
2605
|
-
}
|
|
2606
|
-
function _isFile2(p) {
|
|
2607
|
-
try {
|
|
2608
|
-
return fs7.statSync(p).isFile();
|
|
2609
|
-
} catch {
|
|
2610
|
-
return false;
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
function _isDir2(p) {
|
|
2614
|
-
try {
|
|
2615
|
-
return fs7.statSync(p).isDirectory();
|
|
2616
|
-
} catch {
|
|
2617
|
-
return false;
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
function _load_yaml2(p) {
|
|
2621
|
-
const text = fs7.readFileSync(p, "utf-8");
|
|
2622
|
-
const doc = (0, import_yaml2.parseDocument)(text, { version: "1.1", prettyErrors: false });
|
|
2623
|
-
if (doc.errors.length > 0) {
|
|
2624
|
-
const err = doc.errors[0];
|
|
2625
|
-
throw new Error(err ? err.message : "YAML parse error");
|
|
2626
|
-
}
|
|
2627
|
-
import_yaml2.default.visit(doc, {
|
|
2628
|
-
Scalar(_key, node) {
|
|
2629
|
-
if (!node.range) {
|
|
2630
|
-
return;
|
|
2631
|
-
}
|
|
2632
|
-
const raw = text.slice(node.range[0], node.range[1]);
|
|
2633
|
-
if (node.value instanceof Date) {
|
|
2634
|
-
if (PYYAML_DATE_ONLY_RE2.test(raw)) {
|
|
2635
|
-
node.value = new PyTimestamp2(raw);
|
|
2636
|
-
} else if (PYYAML_TIMESTAMP_RE2.test(raw)) {
|
|
2637
|
-
node.value = new PyTimestamp2(_pyYamlDatetimeStr2(raw));
|
|
2638
|
-
}
|
|
2639
|
-
return;
|
|
2640
|
-
}
|
|
2641
|
-
if (typeof node.value === "boolean" && node.type === "PLAIN" && /^[ynYN]$/.test(raw)) {
|
|
2642
|
-
node.value = raw;
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
});
|
|
2646
|
-
const data = doc.toJS({ mapAsMap: false });
|
|
2647
|
-
return data == null ? {} : data;
|
|
2648
|
-
}
|
|
2649
|
-
function _pyYamlDatetimeStr2(raw) {
|
|
2650
|
-
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[Tt]|[ \t]+)(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d*))?(?:[ \t]*(Z|[-+]\d{1,2}(?::\d{2})?))?$/.exec(
|
|
2651
|
-
raw
|
|
2652
|
-
);
|
|
2653
|
-
if (!m) {
|
|
2654
|
-
return raw;
|
|
2655
|
-
}
|
|
2656
|
-
const [, y, mo, d, h, mi, s, frac, tz] = m;
|
|
2657
|
-
const date = `${y}-${_pad22(mo)}-${_pad22(d)}`;
|
|
2658
|
-
let time = `${_pad22(h)}:${mi}:${s}`;
|
|
2659
|
-
if (frac && /[1-9]/.test(frac)) {
|
|
2660
|
-
time += `.${(frac + "000000").slice(0, 6)}`;
|
|
2661
|
-
}
|
|
2662
|
-
let off = "";
|
|
2663
|
-
if (tz) {
|
|
2664
|
-
if (tz === "Z") {
|
|
2665
|
-
off = "+00:00";
|
|
2666
|
-
} else {
|
|
2667
|
-
const tm = /^([-+])(\d{1,2})(?::(\d{2}))?$/.exec(tz);
|
|
2668
|
-
if (tm) {
|
|
2669
|
-
off = `${tm[1]}${_pad22(tm[2])}:${tm[3] ?? "00"}`;
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
return `${date} ${time}${off}`;
|
|
2674
|
-
}
|
|
2675
|
-
function _pad22(v) {
|
|
2676
|
-
return v.length === 1 ? `0${v}` : v;
|
|
2677
|
-
}
|
|
2678
|
-
function* _iter_curated_entries2(mtype) {
|
|
2679
|
-
const type_dir = path7.join(MEMORY_ROOT2, mtype);
|
|
2680
|
-
const single_file = path7.join(MEMORY_ROOT2, `${mtype}.yml`);
|
|
2681
|
-
if (_isFile2(single_file)) {
|
|
2682
|
-
const data = _load_yaml2(single_file);
|
|
2683
|
-
const entries = _isPlainObject4(data) ? data["entries"] : null;
|
|
2684
|
-
for (const e of Array.isArray(entries) ? entries : []) {
|
|
2685
|
-
if (_isPlainObject4(e)) {
|
|
2686
|
-
if (_statusExcluded(e)) {
|
|
2687
|
-
continue;
|
|
2688
|
-
}
|
|
2689
|
-
yield [single_file, e];
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
if (_isDir2(type_dir)) {
|
|
2694
|
-
for (const yml of _rglobYmlSorted(type_dir)) {
|
|
2695
|
-
const dataRaw = _load_yaml2(yml);
|
|
2696
|
-
const data = dataRaw == null ? {} : dataRaw;
|
|
2697
|
-
const entries = _isPlainObject4(data) ? data["entries"] : void 0;
|
|
2698
|
-
if (Array.isArray(entries)) {
|
|
2699
|
-
for (const e of entries) {
|
|
2700
|
-
if (_isPlainObject4(e)) {
|
|
2701
|
-
if (_statusExcluded(e)) {
|
|
2702
|
-
continue;
|
|
2703
|
-
}
|
|
2704
|
-
yield [yml, e];
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
} else if (_isPlainObject4(data) && data["id"]) {
|
|
2708
|
-
if (_statusExcluded(data)) {
|
|
2709
|
-
continue;
|
|
2710
|
-
}
|
|
2711
|
-
yield [yml, data];
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
}
|
|
2716
|
-
function* _iter_curated_entries_all(mtype) {
|
|
2717
|
-
const type_dir = path7.join(MEMORY_ROOT2, mtype);
|
|
2718
|
-
const single_file = path7.join(MEMORY_ROOT2, `${mtype}.yml`);
|
|
2719
|
-
if (_isFile2(single_file)) {
|
|
2720
|
-
const data = _load_yaml2(single_file);
|
|
2721
|
-
const entries = _isPlainObject4(data) ? data["entries"] : null;
|
|
2722
|
-
for (const e of Array.isArray(entries) ? entries : []) {
|
|
2723
|
-
if (_isPlainObject4(e)) {
|
|
2724
|
-
yield [single_file, e];
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
if (_isDir2(type_dir)) {
|
|
2729
|
-
for (const yml of _rglobYmlSorted(type_dir)) {
|
|
2730
|
-
const dataRaw = _load_yaml2(yml);
|
|
2731
|
-
const data = dataRaw == null ? {} : dataRaw;
|
|
2732
|
-
const entries = _isPlainObject4(data) ? data["entries"] : void 0;
|
|
2733
|
-
if (Array.isArray(entries)) {
|
|
2734
|
-
for (const e of entries) {
|
|
2735
|
-
if (_isPlainObject4(e)) {
|
|
2736
|
-
yield [yml, e];
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
} else if (_isPlainObject4(data) && data["id"]) {
|
|
2740
|
-
yield [yml, data];
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
function* _iter_intake_entries(mtype) {
|
|
2746
|
-
if (!_isDir2(INTAKE_ROOT2)) {
|
|
2747
|
-
return;
|
|
2748
|
-
}
|
|
2749
|
-
for (const jsonl of _globJsonlSorted(INTAKE_ROOT2)) {
|
|
2750
|
-
const by_id = /* @__PURE__ */ new Map();
|
|
2751
|
-
const superseded = /* @__PURE__ */ new Set();
|
|
2752
|
-
const content = fs7.readFileSync(jsonl, "utf-8");
|
|
2753
|
-
for (let line of content.split(/\n/)) {
|
|
2754
|
-
line = line.trim();
|
|
2755
|
-
if (!line) {
|
|
2756
|
-
continue;
|
|
2757
|
-
}
|
|
2758
|
-
let obj;
|
|
2759
|
-
try {
|
|
2760
|
-
obj = JSON.parse(line);
|
|
2761
|
-
} catch {
|
|
2762
|
-
continue;
|
|
2763
|
-
}
|
|
2764
|
-
if (!_isPlainObject4(obj)) {
|
|
2765
|
-
continue;
|
|
2766
|
-
}
|
|
2767
|
-
if (obj["type"] === "supersede") {
|
|
2768
|
-
const target = obj["supersedes"];
|
|
2769
|
-
if (typeof target === "string") {
|
|
2770
|
-
superseded.add(target);
|
|
2771
|
-
}
|
|
2772
|
-
continue;
|
|
2773
|
-
}
|
|
2774
|
-
const eid = obj["id"];
|
|
2775
|
-
if (typeof eid === "string") {
|
|
2776
|
-
by_id.set(eid, obj);
|
|
2777
|
-
}
|
|
2778
|
-
}
|
|
2779
|
-
for (const [eid, obj] of by_id.entries()) {
|
|
2780
|
-
if (superseded.has(eid)) {
|
|
2781
|
-
continue;
|
|
2782
|
-
}
|
|
2783
|
-
if (mtype && obj["entry_type"] && obj["entry_type"] !== mtype) {
|
|
2784
|
-
continue;
|
|
2785
|
-
}
|
|
2786
|
-
yield [jsonl, obj];
|
|
2787
|
-
}
|
|
2788
|
-
}
|
|
2789
|
-
}
|
|
2790
|
-
function* _iter_knowledge_entries() {
|
|
2791
|
-
if (!_isDir2(KNOWLEDGE_ROOT)) {
|
|
2792
|
-
return;
|
|
2793
|
-
}
|
|
2794
|
-
for (const ingest_dir of _iterdirSorted(KNOWLEDGE_ROOT)) {
|
|
2795
|
-
if (!_isDir2(ingest_dir)) {
|
|
2796
|
-
continue;
|
|
2797
|
-
}
|
|
2798
|
-
const manifest_path = path7.join(ingest_dir, "manifest.json");
|
|
2799
|
-
let manifest = {};
|
|
2800
|
-
if (_isFile2(manifest_path)) {
|
|
2801
|
-
try {
|
|
2802
|
-
const parsed = JSON.parse(fs7.readFileSync(manifest_path, "utf-8"));
|
|
2803
|
-
manifest = _isPlainObject4(parsed) ? parsed : {};
|
|
2804
|
-
} catch {
|
|
2805
|
-
manifest = {};
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
const ingest_id = String(manifest["ingest_id"] || path7.basename(ingest_dir));
|
|
2809
|
-
const source = String(manifest["source"] || "");
|
|
2810
|
-
const pinned = Boolean(manifest["pinned"] ?? false);
|
|
2811
|
-
const chunks_dir = path7.join(ingest_dir, "chunks");
|
|
2812
|
-
if (!_isDir2(chunks_dir)) {
|
|
2813
|
-
continue;
|
|
2814
|
-
}
|
|
2815
|
-
for (const chunk of _globMdSorted(chunks_dir)) {
|
|
2816
|
-
let body;
|
|
2817
|
-
try {
|
|
2818
|
-
body = fs7.readFileSync(chunk, "utf-8");
|
|
2819
|
-
} catch {
|
|
2820
|
-
continue;
|
|
2821
|
-
}
|
|
2822
|
-
const entry = {
|
|
2823
|
-
id: `${ingest_id}:${_stem(chunk)}`,
|
|
2824
|
-
ingest_id,
|
|
2825
|
-
source,
|
|
2826
|
-
path: source,
|
|
2827
|
-
body,
|
|
2828
|
-
pinned,
|
|
2829
|
-
source_kind: "knowledge"
|
|
2830
|
-
};
|
|
2831
|
-
yield [chunk, entry];
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
function _score(entry, keys) {
|
|
2836
|
-
if (keys.length === 0) {
|
|
2837
|
-
return 0.1;
|
|
2838
|
-
}
|
|
2839
|
-
const hay_parts = [];
|
|
2840
|
-
for (const field_name of ["path", "key", "symptom", "feature", "rule", "body"]) {
|
|
2841
|
-
const v = entry[field_name];
|
|
2842
|
-
if (typeof v === "string") {
|
|
2843
|
-
hay_parts.push(v);
|
|
2844
|
-
} else if (Array.isArray(v)) {
|
|
2845
|
-
for (const x of v) {
|
|
2846
|
-
hay_parts.push(_pyStr2(x));
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
const hay = hay_parts.join(" | ").toLowerCase();
|
|
2851
|
-
let best = 0;
|
|
2852
|
-
for (const k of keys) {
|
|
2853
|
-
const kl = k.toLowerCase();
|
|
2854
|
-
if (_fnmatch(hay, `*${kl}*`)) {
|
|
2855
|
-
best = Math.max(best, 0.8);
|
|
2856
|
-
} else if (hay.includes(kl)) {
|
|
2857
|
-
best = Math.max(best, 0.6);
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
return best;
|
|
2861
|
-
}
|
|
2862
|
-
function _cross_repo_hits(keys, limit) {
|
|
2863
|
-
const query = keys.filter((k) => k).join(" ").trim();
|
|
2864
|
-
if (!query) {
|
|
2865
|
-
return [];
|
|
2866
|
-
}
|
|
2867
|
-
void limit;
|
|
2868
|
-
return [];
|
|
2869
|
-
}
|
|
2870
|
-
function _today() {
|
|
2871
|
-
const now = /* @__PURE__ */ new Date();
|
|
2872
|
-
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
2873
|
-
}
|
|
2874
|
-
function _parseIsoDate2(s) {
|
|
2875
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
2876
|
-
if (!m) {
|
|
2877
|
-
return null;
|
|
2878
|
-
}
|
|
2879
|
-
const y = Number(m[1]);
|
|
2880
|
-
const mo = Number(m[2]);
|
|
2881
|
-
const d = Number(m[3]);
|
|
2882
|
-
if (mo < 1 || mo > 12 || d < 1 || d > 31) {
|
|
2883
|
-
return null;
|
|
2884
|
-
}
|
|
2885
|
-
const date = new Date(Date.UTC(y, mo - 1, d));
|
|
2886
|
-
if (date.getUTCFullYear() !== y || date.getUTCMonth() !== mo - 1 || date.getUTCDate() !== d) {
|
|
2887
|
-
return null;
|
|
2888
|
-
}
|
|
2889
|
-
return date;
|
|
2890
|
-
}
|
|
2891
|
-
function _pyStrDateLike(lv) {
|
|
2892
|
-
if (lv instanceof PyTimestamp2) {
|
|
2893
|
-
return lv.pyStr;
|
|
2894
|
-
}
|
|
2895
|
-
return _pyStr2(lv);
|
|
2896
|
-
}
|
|
2897
|
-
function _isStale(entry, today) {
|
|
2898
|
-
const lv = entry["last_validated"];
|
|
2899
|
-
const rad = entry["review_after_days"];
|
|
2900
|
-
if (!lv || typeof rad !== "number" || !Number.isInteger(rad)) {
|
|
2901
|
-
return false;
|
|
2902
|
-
}
|
|
2903
|
-
const validated = _parseIsoDate2(_pyStrDateLike(lv));
|
|
2904
|
-
if (validated === null) {
|
|
2905
|
-
return false;
|
|
2906
|
-
}
|
|
2907
|
-
const days = Math.round((today.getTime() - validated.getTime()) / 864e5);
|
|
2908
|
-
return days > rad;
|
|
2909
|
-
}
|
|
2910
|
-
function _entry_id(mtype, file, entry, ordinals) {
|
|
2911
|
-
const ordinal = ordinals.get(file) ?? 0;
|
|
2912
|
-
ordinals.set(file, ordinal + 1);
|
|
2913
|
-
const own = entry["id"];
|
|
2914
|
-
if (typeof own === "string" && own.trim() !== "") {
|
|
2915
|
-
return own;
|
|
2916
|
-
}
|
|
2917
|
-
if (own !== void 0 && own !== null && String(own).trim() !== "") {
|
|
2918
|
-
return String(own);
|
|
2919
|
-
}
|
|
2920
|
-
return `${mtype}:${path7.basename(file)}:${ordinal}`;
|
|
2921
|
-
}
|
|
2922
|
-
function _entry_title(entry, id) {
|
|
2923
|
-
for (const field of ["title", "key", "path"]) {
|
|
2924
|
-
const v = entry[field];
|
|
2925
|
-
if (typeof v === "string" && v.trim() !== "") {
|
|
2926
|
-
return v.trim().slice(0, 120);
|
|
2927
|
-
}
|
|
2928
|
-
}
|
|
2929
|
-
const body = entry["body"];
|
|
2930
|
-
if (typeof body === "string" && body.trim() !== "") {
|
|
2931
|
-
const first = body.trim().split("\n")[0];
|
|
2932
|
-
return first.trim().slice(0, 120);
|
|
2933
|
-
}
|
|
2934
|
-
return id;
|
|
2935
|
-
}
|
|
2936
|
-
function _compact_line(hit, safeEntry) {
|
|
2937
|
-
const head = _entry_title(safeEntry, hit.id).replace(/\s+/g, " ").trim();
|
|
2938
|
-
const score = hit.score.toFixed(2);
|
|
2939
|
-
return `HIT ${hit.type}/${hit.id} [src=${hit.path} score=${score}] ${head}`;
|
|
2940
|
-
}
|
|
2941
|
-
var _tokenizer;
|
|
2942
|
-
function _lazy_tokenizer() {
|
|
2943
|
-
if (_tokenizer === void 0) {
|
|
2944
|
-
try {
|
|
2945
|
-
const req = createRequire2(import.meta.url);
|
|
2946
|
-
_tokenizer = req("./_lib/token_count.js");
|
|
2947
|
-
} catch {
|
|
2948
|
-
_tokenizer = null;
|
|
2949
|
-
}
|
|
2950
|
-
}
|
|
2951
|
-
return _tokenizer;
|
|
2952
|
-
}
|
|
2953
|
-
function _entry_tokens_estimate(envelope_entry) {
|
|
2954
|
-
const serialized = JSON.stringify(envelope_entry);
|
|
2955
|
-
const tok = _lazy_tokenizer();
|
|
2956
|
-
if (tok !== null && tok !== void 0 && tok.TIKTOKEN_AVAILABLE) {
|
|
2957
|
-
return tok.gpt_tokens(serialized).tokens;
|
|
2958
|
-
}
|
|
2959
|
-
return Math.ceil(serialized.length / 4);
|
|
2960
|
-
}
|
|
2961
|
-
function _countMdFiles(dir) {
|
|
2962
|
-
let entries;
|
|
2963
|
-
try {
|
|
2964
|
-
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
2965
|
-
} catch {
|
|
2966
|
-
return 0;
|
|
2967
|
-
}
|
|
2968
|
-
let n = 0;
|
|
2969
|
-
for (const e of entries) {
|
|
2970
|
-
if (e.isDirectory()) n += _countMdFiles(path7.join(dir, e.name));
|
|
2971
|
-
else if (e.isFile() && e.name.endsWith(".md")) n += 1;
|
|
2972
|
-
}
|
|
2973
|
-
return n;
|
|
2974
|
-
}
|
|
2975
|
-
function _countCorpusFiles() {
|
|
2976
|
-
if (_corpusFileCounts !== null) return _corpusFileCounts;
|
|
2977
|
-
const perType = /* @__PURE__ */ new Map();
|
|
2978
|
-
let total = 0;
|
|
2979
|
-
for (const t of CURATED_TYPES2) {
|
|
2980
|
-
const exists = fs7.existsSync(path7.join(MEMORY_ROOT2, `${t}.yml`));
|
|
2981
|
-
const c = exists ? 1 : 0;
|
|
2982
|
-
perType.set(t, c);
|
|
2983
|
-
total += c;
|
|
2984
|
-
}
|
|
2985
|
-
const kn = _countMdFiles(KNOWLEDGE_ROOT);
|
|
2986
|
-
perType.set(KNOWLEDGE_TYPE, kn);
|
|
2987
|
-
total += kn;
|
|
2988
|
-
try {
|
|
2989
|
-
for (const e of fs7.readdirSync(INTAKE_ROOT2, { withFileTypes: true })) {
|
|
2990
|
-
if (e.isFile() && e.name.endsWith(".jsonl")) total += 1;
|
|
2991
|
-
}
|
|
2992
|
-
} catch {
|
|
2993
|
-
}
|
|
2994
|
-
_corpusFileCounts = { perType, total };
|
|
2995
|
-
return _corpusFileCounts;
|
|
2996
|
-
}
|
|
2997
|
-
function _lexicalRankActive(types) {
|
|
2998
|
-
const typeMax = _lexTripwireOverride ?? _LEX_TYPE_FILES_MAX;
|
|
2999
|
-
const corpusMax = _lexTripwireOverride ?? _LEX_CORPUS_FILES_MAX;
|
|
3000
|
-
const counts = _countCorpusFiles();
|
|
3001
|
-
if (counts.total > corpusMax) return true;
|
|
3002
|
-
for (const t of types) {
|
|
3003
|
-
if ((counts.perType.get(t) ?? 0) > typeMax) return true;
|
|
3004
|
-
}
|
|
3005
|
-
return false;
|
|
3006
|
-
}
|
|
3007
|
-
function _entryIndexText2(entry) {
|
|
3008
|
-
const parts = [];
|
|
3009
|
-
for (const f of ["path", "key", "symptom", "feature", "rule", "body"]) {
|
|
3010
|
-
const v = entry[f];
|
|
3011
|
-
if (typeof v === "string") parts.push(v);
|
|
3012
|
-
else if (Array.isArray(v)) for (const x of v) parts.push(_pyStr2(x));
|
|
3013
|
-
}
|
|
3014
|
-
return parts.join(" | ");
|
|
3015
|
-
}
|
|
3016
|
-
function _lexicalRerank(hits, keys) {
|
|
3017
|
-
if (hits.length === 0) return;
|
|
3018
|
-
const index = new LexicalIndex(hits.map((h) => ({ id: h.id, text: _entryIndexText2(h.entry) })));
|
|
3019
|
-
const qTerms = tokenize(keys.join(" "));
|
|
3020
|
-
for (const h of hits) {
|
|
3021
|
-
const bm25 = index.score(qTerms, h.id);
|
|
3022
|
-
const factor = _LEX_SOURCE_FACTOR[h.source] ?? 0.8;
|
|
3023
|
-
h.score = bm25 > 0 ? bm25 / (bm25 + 1) * factor : h.score * 1e-6;
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3026
|
-
var _FTS_RECALL_GAP_SOURCE_FACTOR = { curated: 0.5, intake: 0.45, knowledge: 0.4 };
|
|
3027
|
-
function _ftsRecallGapHits(repo_hits, keys) {
|
|
3028
|
-
if (repo_hits.length === 0 || keys.length === 0) return [];
|
|
3029
|
-
const indexPath = resolveIndexPath();
|
|
3030
|
-
if (!ensureIndex(indexPath, MEMORY_ROOT2, INTAKE_ROOT2)) return [];
|
|
3031
|
-
const types = [...new Set(repo_hits.map((h) => h.type))];
|
|
3032
|
-
const ftsHits = queryIndex(indexPath, keys, types);
|
|
3033
|
-
if (ftsHits.length === 0) return [];
|
|
3034
|
-
const byId = new Map(ftsHits.map((h) => [h.id, h]));
|
|
3035
|
-
const out = [];
|
|
3036
|
-
for (const h of repo_hits) {
|
|
3037
|
-
const fts = byId.get(h.id);
|
|
3038
|
-
if (fts === void 0) continue;
|
|
3039
|
-
const factor = _FTS_RECALL_GAP_SOURCE_FACTOR[h.source] ?? 0.4;
|
|
3040
|
-
const score = fts.relevance / (fts.relevance + 1) * factor;
|
|
3041
|
-
if (score > 0) out.push(new Hit(h.id, h.type, h.source, h.path, score, h.entry));
|
|
3042
|
-
}
|
|
3043
|
-
return out;
|
|
3044
|
-
}
|
|
3045
|
-
function _retrieve_internal(types, keys, limit, today) {
|
|
3046
|
-
const repo_hits = [];
|
|
3047
|
-
const skipped = [];
|
|
3048
|
-
for (const mtype of types) {
|
|
3049
|
-
if (mtype === KNOWLEDGE_TYPE) {
|
|
3050
|
-
for (const [p, entry] of _iter_knowledge_entries()) {
|
|
3051
|
-
let base = _score(entry, keys);
|
|
3052
|
-
if (entry["pinned"]) {
|
|
3053
|
-
base = Math.min(1, base + 0.05);
|
|
3054
|
-
}
|
|
3055
|
-
repo_hits.push(
|
|
3056
|
-
new Hit(
|
|
3057
|
-
String(entry["id"] ?? ""),
|
|
3058
|
-
KNOWLEDGE_TYPE,
|
|
3059
|
-
"knowledge",
|
|
3060
|
-
String(p),
|
|
3061
|
-
// Discount vs curated/intake so hand-reviewed repo
|
|
3062
|
-
// entries still win on equal relevance.
|
|
3063
|
-
base * 0.85,
|
|
3064
|
-
entry
|
|
3065
|
-
)
|
|
3066
|
-
);
|
|
3067
|
-
}
|
|
3068
|
-
continue;
|
|
3069
|
-
}
|
|
3070
|
-
if (mtype === CROSS_REPO_TYPE) {
|
|
3071
|
-
for (const h of _cross_repo_hits(keys, limit)) {
|
|
3072
|
-
repo_hits.push(h);
|
|
3073
|
-
}
|
|
3074
|
-
continue;
|
|
3075
|
-
}
|
|
3076
|
-
if (!CURATED_TYPES2.has(mtype)) {
|
|
3077
|
-
continue;
|
|
3078
|
-
}
|
|
3079
|
-
const _cur_ordinals = /* @__PURE__ */ new Map();
|
|
3080
|
-
for (const [p, entry] of _iter_curated_entries2(mtype)) {
|
|
3081
|
-
const eid = _entry_id(mtype, String(p), entry, _cur_ordinals);
|
|
3082
|
-
if (_isStale(entry, today)) {
|
|
3083
|
-
const lv = "last_validated" in entry ? _pyStrDateLike(entry["last_validated"]) : "unknown";
|
|
3084
|
-
const rad = "review_after_days" in entry ? _pyStr2(entry["review_after_days"]) : "?";
|
|
3085
|
-
skipped.push({
|
|
3086
|
-
id: eid,
|
|
3087
|
-
type: mtype,
|
|
3088
|
-
reason: "stale",
|
|
3089
|
-
details: `last_validated=${lv}, review_after_days=${rad}`
|
|
3090
|
-
});
|
|
3091
|
-
continue;
|
|
3092
|
-
}
|
|
3093
|
-
repo_hits.push(new Hit(eid, mtype, "curated", String(p), _score(entry, keys), entry));
|
|
3094
|
-
}
|
|
3095
|
-
for (const [p, entry] of _iter_curated_entries_all(mtype)) {
|
|
3096
|
-
void p;
|
|
3097
|
-
const status2 = "status" in entry && entry["status"] !== void 0 ? entry["status"] : "active";
|
|
3098
|
-
if (typeof status2 === "string" && _CURATED_STATUS_EXCLUDE.has(status2)) {
|
|
3099
|
-
skipped.push({
|
|
3100
|
-
id: String(entry["id"] ?? ""),
|
|
3101
|
-
type: mtype,
|
|
3102
|
-
reason: "superseded",
|
|
3103
|
-
details: `status=${_pyStr2(status2)}`
|
|
3104
|
-
});
|
|
3105
|
-
}
|
|
3106
|
-
}
|
|
3107
|
-
for (const [p, entry] of _iter_intake_entries(mtype)) {
|
|
3108
|
-
repo_hits.push(
|
|
3109
|
-
new Hit(
|
|
3110
|
-
String(entry["id"] ?? ""),
|
|
3111
|
-
mtype,
|
|
3112
|
-
"intake",
|
|
3113
|
-
String(p),
|
|
3114
|
-
_score(entry, keys) * 0.9,
|
|
3115
|
-
// slight discount vs curated
|
|
3116
|
-
entry
|
|
3117
|
-
)
|
|
3118
|
-
);
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
_sortMerged(repo_hits);
|
|
3122
|
-
let positives = repo_hits.filter((h) => h.score > 0);
|
|
3123
|
-
if (positives.length > 0 && _lexicalRankActive(types)) {
|
|
3124
|
-
_lexicalRerank(positives, keys);
|
|
3125
|
-
_sortMerged(positives);
|
|
3126
|
-
positives = positives.filter((h) => h.score > 0);
|
|
3127
|
-
}
|
|
3128
|
-
if (positives.length === 0 && repo_hits.length > 0) {
|
|
3129
|
-
const ftsHits = _ftsRecallGapHits(repo_hits, keys);
|
|
3130
|
-
if (ftsHits.length > 0) {
|
|
3131
|
-
_sortMerged(ftsHits);
|
|
3132
|
-
positives = ftsHits.filter((h) => h.score > 0);
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
const hits = (positives.length > 0 ? positives : repo_hits).slice(0, limit);
|
|
3136
|
-
for (const h of hits) {
|
|
3137
|
-
if (_isPlainObject4(h.entry)) h.entry = sanitize_entry(h.entry);
|
|
3138
|
-
}
|
|
3139
|
-
return [hits, skipped];
|
|
3140
|
-
}
|
|
3141
|
-
function retrieve(types, keys, limit = 5) {
|
|
3142
|
-
const [hits] = _retrieve_internal(types, keys, limit, _today());
|
|
3143
|
-
return hits;
|
|
3144
|
-
}
|
|
3145
|
-
function _sortMerged(merged) {
|
|
3146
|
-
const indexed = merged.map((h, i) => ({ h, i }));
|
|
3147
|
-
indexed.sort((a, b) => {
|
|
3148
|
-
if (a.h.score !== b.h.score) {
|
|
3149
|
-
return b.h.score - a.h.score;
|
|
3150
|
-
}
|
|
3151
|
-
const ac = a.h.source === "curated" ? 1 : 0;
|
|
3152
|
-
const bc = b.h.source === "curated" ? 1 : 0;
|
|
3153
|
-
if (ac !== bc) {
|
|
3154
|
-
return bc - ac;
|
|
3155
|
-
}
|
|
3156
|
-
return a.i - b.i;
|
|
3157
|
-
});
|
|
3158
|
-
for (let i = 0; i < indexed.length; i += 1) {
|
|
3159
|
-
merged[i] = indexed[i].h;
|
|
3160
|
-
}
|
|
3161
|
-
}
|
|
3162
|
-
var _CONTRACT_VERSION = 1;
|
|
3163
|
-
var _KNOWN_TYPES = /* @__PURE__ */ new Set([...CURATED_TYPES2, KNOWLEDGE_TYPE, CROSS_REPO_TYPE]);
|
|
3164
|
-
function retrieve_v1(types, keys, limit = 20, options = {}) {
|
|
3165
|
-
const detail = options.detail ?? "full";
|
|
3166
|
-
const rawBudget = options.token_budget;
|
|
3167
|
-
const budgeted = typeof rawBudget === "number" && Number.isFinite(rawBudget) && rawBudget > 0;
|
|
3168
|
-
const budgetChars = budgeted ? Math.floor(rawBudget * 4) : 0;
|
|
3169
|
-
const known = types.filter((t) => _KNOWN_TYPES.has(t));
|
|
3170
|
-
const unknown = types.filter((t) => !_KNOWN_TYPES.has(t));
|
|
3171
|
-
const hits = retrieve(known, keys, limit);
|
|
3172
|
-
const slice_counts = {};
|
|
3173
|
-
for (const t of known) {
|
|
3174
|
-
slice_counts[t] = 0;
|
|
3175
|
-
}
|
|
3176
|
-
const entries = [];
|
|
3177
|
-
let usedChars = 0;
|
|
3178
|
-
let truncation = null;
|
|
3179
|
-
for (let hi = 0; hi < hits.length; hi++) {
|
|
3180
|
-
const h = hits[hi];
|
|
3181
|
-
const safeEntry = _isPlainObject4(h.entry) ? sanitize_entry(h.entry) : {};
|
|
3182
|
-
const learning = _learningVerdictFor(h.type, safeEntry);
|
|
3183
|
-
if (learning !== void 0) safeEntry["learning"] = learning;
|
|
3184
|
-
if (budgeted) {
|
|
3185
|
-
const line = _compact_line(h, safeEntry);
|
|
3186
|
-
const cost = line.length + 1;
|
|
3187
|
-
if (usedChars + cost > budgetChars && entries.length > 0) {
|
|
3188
|
-
const omitted = hits.length - hi;
|
|
3189
|
-
const nextPath = h.path || "<path>";
|
|
3190
|
-
truncation = {
|
|
3191
|
-
omitted,
|
|
3192
|
-
hint: `${omitted} more hit(s) \u2014 narrow with --key or read ${nextPath}`
|
|
3193
|
-
};
|
|
3194
|
-
break;
|
|
3195
|
-
}
|
|
3196
|
-
usedChars += cost;
|
|
3197
|
-
entries.push({
|
|
3198
|
-
id: h.id,
|
|
3199
|
-
type: h.type,
|
|
3200
|
-
source: "repo",
|
|
3201
|
-
confidence: new PyFloat3(_round4(h.score)),
|
|
3202
|
-
line,
|
|
3203
|
-
...learning !== void 0 ? { learning } : {}
|
|
3204
|
-
});
|
|
3205
|
-
if (h.type in slice_counts) {
|
|
3206
|
-
slice_counts[h.type] = slice_counts[h.type] + 1;
|
|
3207
|
-
}
|
|
3208
|
-
continue;
|
|
3209
|
-
}
|
|
3210
|
-
const envelope_entry = {
|
|
3211
|
-
id: h.id,
|
|
3212
|
-
type: h.type,
|
|
3213
|
-
source: "repo",
|
|
3214
|
-
// confidence = round(float(score), 4) — always a Python float.
|
|
3215
|
-
confidence: new PyFloat3(_round4(h.score)),
|
|
3216
|
-
body: safeEntry
|
|
3217
|
-
};
|
|
3218
|
-
if (h.type in slice_counts) {
|
|
3219
|
-
slice_counts[h.type] = slice_counts[h.type] + 1;
|
|
3220
|
-
}
|
|
3221
|
-
if (detail === "index") {
|
|
3222
|
-
entries.push({
|
|
3223
|
-
id: h.id,
|
|
3224
|
-
type: h.type,
|
|
3225
|
-
source: "repo",
|
|
3226
|
-
confidence: new PyFloat3(_round4(h.score)),
|
|
3227
|
-
title: _entry_title(safeEntry, h.id),
|
|
3228
|
-
tokens_estimate: _entry_tokens_estimate(envelope_entry),
|
|
3229
|
-
// Knowledge chunks carry their pinned flag in the index row
|
|
3230
|
-
// (roadmap Phase 4) — pinned entries rank higher, so the
|
|
3231
|
-
// caller should see why before fetching.
|
|
3232
|
-
...h.type === KNOWLEDGE_TYPE ? { pinned: Boolean(safeEntry["pinned"] ?? false) } : {},
|
|
3233
|
-
...learning !== void 0 ? { learning } : {}
|
|
3234
|
-
});
|
|
3235
|
-
} else {
|
|
3236
|
-
entries.push(envelope_entry);
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
const slices = {};
|
|
3240
|
-
for (const t of known) {
|
|
3241
|
-
slices[t] = { status: "ok", count: slice_counts[t] ?? 0 };
|
|
3242
|
-
}
|
|
3243
|
-
const errors = [];
|
|
3244
|
-
for (const t of unknown) {
|
|
3245
|
-
slices[t] = { status: "unknown_type", count: 0 };
|
|
3246
|
-
errors.push({
|
|
3247
|
-
type: t,
|
|
3248
|
-
code: "unknown_type",
|
|
3249
|
-
message: `file-backed backend does not know type ${_pyRepr(t)}`
|
|
3250
|
-
});
|
|
3251
|
-
}
|
|
3252
|
-
const sliceVals = Object.values(slices);
|
|
3253
|
-
const oks = sliceVals.filter((s) => s["status"] === "ok");
|
|
3254
|
-
const fails = sliceVals.filter((s) => s["status"] !== "ok");
|
|
3255
|
-
const envelope_status = fails.length === 0 ? "ok" : oks.length === 0 ? "error" : "partial";
|
|
3256
|
-
const envelope = {
|
|
3257
|
-
contract_version: _CONTRACT_VERSION,
|
|
3258
|
-
status: envelope_status,
|
|
3259
|
-
entries,
|
|
3260
|
-
slices
|
|
3261
|
-
};
|
|
3262
|
-
if (errors.length > 0) {
|
|
3263
|
-
envelope["errors"] = errors;
|
|
3264
|
-
}
|
|
3265
|
-
if (truncation !== null) {
|
|
3266
|
-
envelope["truncation"] = truncation;
|
|
3267
|
-
}
|
|
3268
|
-
return envelope;
|
|
3269
|
-
}
|
|
3270
|
-
function memory_get_v1(ids) {
|
|
3271
|
-
const wanted = new Set(ids);
|
|
3272
|
-
const found = /* @__PURE__ */ new Map();
|
|
3273
|
-
const record = (id, mtype, entry) => {
|
|
3274
|
-
if (wanted.has(id) && !found.has(id)) {
|
|
3275
|
-
const safeEntry = _isPlainObject4(entry) ? sanitize_entry(entry) : {};
|
|
3276
|
-
const learning = _learningVerdictFor(mtype, safeEntry);
|
|
3277
|
-
if (learning !== void 0) safeEntry["learning"] = learning;
|
|
3278
|
-
found.set(id, {
|
|
3279
|
-
id,
|
|
3280
|
-
type: mtype,
|
|
3281
|
-
source: "repo",
|
|
3282
|
-
body: safeEntry
|
|
3283
|
-
});
|
|
3284
|
-
}
|
|
3285
|
-
};
|
|
3286
|
-
for (const mtype of CURATED_TYPES2) {
|
|
3287
|
-
const ordinals = /* @__PURE__ */ new Map();
|
|
3288
|
-
for (const [p, entry] of _iter_curated_entries2(mtype)) {
|
|
3289
|
-
record(_entry_id(mtype, String(p), entry, ordinals), mtype, entry);
|
|
3290
|
-
}
|
|
3291
|
-
for (const [p, entry] of _iter_intake_entries(mtype)) {
|
|
3292
|
-
void p;
|
|
3293
|
-
const own = entry["id"];
|
|
3294
|
-
if (typeof own === "string" && own.trim() !== "") {
|
|
3295
|
-
record(own, mtype, entry);
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
}
|
|
3299
|
-
for (const [p, entry] of _iter_knowledge_entries()) {
|
|
3300
|
-
void p;
|
|
3301
|
-
record(String(entry["id"] ?? ""), KNOWLEDGE_TYPE, entry);
|
|
3302
|
-
}
|
|
3303
|
-
const entries = [];
|
|
3304
|
-
const idStatus = {};
|
|
3305
|
-
for (const id of ids) {
|
|
3306
|
-
const e = found.get(id);
|
|
3307
|
-
if (e !== void 0) {
|
|
3308
|
-
entries.push(e);
|
|
3309
|
-
idStatus[id] = "ok";
|
|
3310
|
-
} else {
|
|
3311
|
-
idStatus[id] = "unknown";
|
|
3312
|
-
}
|
|
3313
|
-
}
|
|
3314
|
-
const okCount = entries.length;
|
|
3315
|
-
const status2 = okCount === ids.length ? "ok" : okCount === 0 ? "error" : "partial";
|
|
3316
|
-
return {
|
|
3317
|
-
contract_version: _CONTRACT_VERSION,
|
|
3318
|
-
status: status2,
|
|
3319
|
-
entries,
|
|
3320
|
-
ids: idStatus
|
|
3321
|
-
};
|
|
3322
|
-
}
|
|
3323
|
-
function _parseArgs3(argv) {
|
|
3324
|
-
const args = {
|
|
3325
|
-
types: "",
|
|
3326
|
-
key: [],
|
|
3327
|
-
limit: 5,
|
|
3328
|
-
format: "text",
|
|
3329
|
-
envelope: "legacy",
|
|
3330
|
-
detail: "full",
|
|
3331
|
-
tokenBudget: null
|
|
3332
|
-
};
|
|
3333
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
3334
|
-
const a = argv[i];
|
|
3335
|
-
if (a === "--types") {
|
|
3336
|
-
args.types = argv[++i];
|
|
3337
|
-
} else if (a.startsWith("--types=")) {
|
|
3338
|
-
args.types = a.slice("--types=".length);
|
|
3339
|
-
} else if (a === "--key") {
|
|
3340
|
-
args.key.push(argv[++i]);
|
|
3341
|
-
} else if (a.startsWith("--key=")) {
|
|
3342
|
-
args.key.push(a.slice("--key=".length));
|
|
3343
|
-
} else if (a === "--limit") {
|
|
3344
|
-
args.limit = _parseInt(argv[++i], "--limit");
|
|
3345
|
-
} else if (a.startsWith("--limit=")) {
|
|
3346
|
-
args.limit = _parseInt(a.slice("--limit=".length), "--limit");
|
|
3347
|
-
} else if (a === "--format") {
|
|
3348
|
-
args.format = _checkChoice2(argv[++i], ["text", "json"], "--format");
|
|
3349
|
-
} else if (a.startsWith("--format=")) {
|
|
3350
|
-
args.format = _checkChoice2(a.slice("--format=".length), ["text", "json"], "--format");
|
|
3351
|
-
} else if (a === "--envelope") {
|
|
3352
|
-
args.envelope = _checkChoice2(argv[++i], ["legacy", "v1"], "--envelope");
|
|
3353
|
-
} else if (a.startsWith("--envelope=")) {
|
|
3354
|
-
args.envelope = _checkChoice2(a.slice("--envelope=".length), ["legacy", "v1"], "--envelope");
|
|
3355
|
-
} else if (a === "--detail") {
|
|
3356
|
-
args.detail = _checkChoice2(argv[++i], ["index", "full"], "--detail");
|
|
3357
|
-
} else if (a.startsWith("--detail=")) {
|
|
3358
|
-
args.detail = _checkChoice2(a.slice("--detail=".length), ["index", "full"], "--detail");
|
|
3359
|
-
} else if (a === "--token-budget") {
|
|
3360
|
-
args.tokenBudget = _parseInt(argv[++i], "--token-budget");
|
|
3361
|
-
} else if (a.startsWith("--token-budget=")) {
|
|
3362
|
-
args.tokenBudget = _parseInt(a.slice("--token-budget=".length), "--token-budget");
|
|
3363
|
-
} else if (a === "-h" || a === "--help") {
|
|
3364
|
-
_printUsage();
|
|
3365
|
-
process5.exit(0);
|
|
3366
|
-
} else {
|
|
3367
|
-
process5.stderr.write(`memory_lookup: error: unrecognized arguments: ${a}
|
|
3368
|
-
`);
|
|
3369
|
-
process5.exit(2);
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
return args;
|
|
3373
|
-
}
|
|
3374
|
-
function _parseInt(value, flag) {
|
|
3375
|
-
const n = Number(value);
|
|
3376
|
-
if (value === void 0 || !Number.isInteger(n)) {
|
|
3377
|
-
process5.stderr.write(
|
|
3378
|
-
`memory_lookup: error: argument ${flag}: invalid int value: '${value ?? ""}'
|
|
3379
|
-
`
|
|
3380
|
-
);
|
|
3381
|
-
process5.exit(2);
|
|
3382
|
-
}
|
|
3383
|
-
return n;
|
|
3384
|
-
}
|
|
3385
|
-
function _checkChoice2(value, choices, flag) {
|
|
3386
|
-
if (value === void 0 || !choices.includes(value)) {
|
|
3387
|
-
process5.stderr.write(
|
|
3388
|
-
`memory_lookup: error: argument ${flag}: invalid choice: '${value ?? ""}' (choose from ${choices.map((c) => `'${c}'`).join(", ")})
|
|
3389
|
-
`
|
|
3390
|
-
);
|
|
3391
|
-
process5.exit(2);
|
|
3392
|
-
}
|
|
3393
|
-
return value;
|
|
3394
|
-
}
|
|
3395
|
-
function _printUsage() {
|
|
3396
|
-
process5.stdout.write(
|
|
3397
|
-
"usage: memory_lookup [-h] [--types TYPES] [--key KEY] [--limit LIMIT] [--format {text,json}] [--envelope {legacy,v1}] [--detail {index,full}] [--token-budget N]\n"
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
3400
|
-
function main5() {
|
|
3401
|
-
const args = _parseArgs3(process5.argv.slice(2));
|
|
3402
|
-
const types = args.types.split(",").map((t) => t.trim()).filter((t) => t);
|
|
3403
|
-
if (types.length === 0) {
|
|
3404
|
-
process5.stderr.write("error: --types is required\n");
|
|
3405
|
-
return 2;
|
|
3406
|
-
}
|
|
3407
|
-
if (args.envelope === "v1") {
|
|
3408
|
-
const opts = { detail: args.detail };
|
|
3409
|
-
if (args.tokenBudget !== null) {
|
|
3410
|
-
opts.token_budget = args.tokenBudget;
|
|
3411
|
-
}
|
|
3412
|
-
const envelope = retrieve_v1(types, args.key, args.limit, opts);
|
|
3413
|
-
process5.stdout.write(`${pyJsonDumps2(envelope, 2)}
|
|
3414
|
-
`);
|
|
3415
|
-
return 0;
|
|
3416
|
-
}
|
|
3417
|
-
const hits = retrieve(types, args.key, args.limit);
|
|
3418
|
-
if (args.format === "json") {
|
|
3419
|
-
const payload = {
|
|
3420
|
-
hits: hits.map((h) => h.as_dict())
|
|
3421
|
-
};
|
|
3422
|
-
process5.stdout.write(`${pyJsonDumps2(payload, 2)}
|
|
3423
|
-
`);
|
|
3424
|
-
} else {
|
|
3425
|
-
if (hits.length === 0) {
|
|
3426
|
-
process5.stdout.write(" (no hits)\n");
|
|
3427
|
-
}
|
|
3428
|
-
for (const h of hits) {
|
|
3429
|
-
process5.stdout.write(
|
|
3430
|
-
` [${h.source}] ${h.type} score=${_fixed2(h.score)} id=${h.id || "-"} path=${h.path}
|
|
3431
|
-
`
|
|
3432
|
-
);
|
|
3433
|
-
}
|
|
3434
|
-
}
|
|
3435
|
-
return 0;
|
|
3436
|
-
}
|
|
3437
|
-
function _fixed2(n) {
|
|
3438
|
-
return n.toFixed(2);
|
|
3439
|
-
}
|
|
3440
|
-
function _round4(n) {
|
|
3441
|
-
return _pyRound(n, 4);
|
|
3442
|
-
}
|
|
3443
|
-
function _pyRound(value, ndigits) {
|
|
3444
|
-
const factor = 10 ** ndigits;
|
|
3445
|
-
const scaled = value * factor;
|
|
3446
|
-
const floor = Math.floor(scaled);
|
|
3447
|
-
const diff = scaled - floor;
|
|
3448
|
-
let rounded;
|
|
3449
|
-
if (diff > 0.5) {
|
|
3450
|
-
rounded = floor + 1;
|
|
3451
|
-
} else if (diff < 0.5) {
|
|
3452
|
-
rounded = floor;
|
|
3453
|
-
} else {
|
|
3454
|
-
rounded = floor % 2 === 0 ? floor : floor + 1;
|
|
3455
|
-
}
|
|
3456
|
-
return rounded / factor;
|
|
3457
|
-
}
|
|
3458
|
-
function _fnmatch(name, pattern) {
|
|
3459
|
-
const re = _fnmatchTranslate(pattern);
|
|
3460
|
-
return re.test(name);
|
|
3461
|
-
}
|
|
3462
|
-
function _fnmatchTranslate(pat) {
|
|
3463
|
-
let res = "";
|
|
3464
|
-
let i = 0;
|
|
3465
|
-
const n = pat.length;
|
|
3466
|
-
while (i < n) {
|
|
3467
|
-
const c = pat[i];
|
|
3468
|
-
i += 1;
|
|
3469
|
-
if (c === "*") {
|
|
3470
|
-
res += ".*";
|
|
3471
|
-
} else if (c === "?") {
|
|
3472
|
-
res += ".";
|
|
3473
|
-
} else if (c === "[") {
|
|
3474
|
-
let j = i;
|
|
3475
|
-
if (j < n && pat[j] === "!") {
|
|
3476
|
-
j += 1;
|
|
3477
|
-
}
|
|
3478
|
-
if (j < n && pat[j] === "]") {
|
|
3479
|
-
j += 1;
|
|
3480
|
-
}
|
|
3481
|
-
while (j < n && pat[j] !== "]") {
|
|
3482
|
-
j += 1;
|
|
3483
|
-
}
|
|
3484
|
-
if (j >= n) {
|
|
3485
|
-
res += "\\[";
|
|
3486
|
-
} else {
|
|
3487
|
-
let stuff = pat.slice(i, j);
|
|
3488
|
-
if (!stuff.includes("-")) {
|
|
3489
|
-
stuff = stuff.replace(/\\/g, "\\\\");
|
|
3490
|
-
} else {
|
|
3491
|
-
stuff = stuff.replace(/\\/g, "\\\\");
|
|
3492
|
-
}
|
|
3493
|
-
i = j + 1;
|
|
3494
|
-
if (stuff.startsWith("!")) {
|
|
3495
|
-
stuff = `^${stuff.slice(1)}`;
|
|
3496
|
-
} else if (stuff.startsWith("^") || stuff.startsWith("[")) {
|
|
3497
|
-
stuff = `\\${stuff}`;
|
|
3498
|
-
}
|
|
3499
|
-
res += `[${stuff}]`;
|
|
3500
|
-
}
|
|
3501
|
-
} else {
|
|
3502
|
-
res += _reEscape(c);
|
|
3503
|
-
}
|
|
3504
|
-
}
|
|
3505
|
-
return new RegExp(`^(?:${res})$`, "s");
|
|
3506
|
-
}
|
|
3507
|
-
function _reEscape(ch) {
|
|
3508
|
-
return ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3509
|
-
}
|
|
3510
|
-
function _pyStr2(value) {
|
|
3511
|
-
if (value === null || value === void 0) {
|
|
3512
|
-
return "None";
|
|
3513
|
-
}
|
|
3514
|
-
if (value === true) {
|
|
3515
|
-
return "True";
|
|
3516
|
-
}
|
|
3517
|
-
if (value === false) {
|
|
3518
|
-
return "False";
|
|
3519
|
-
}
|
|
3520
|
-
return String(value);
|
|
3521
|
-
}
|
|
3522
|
-
function _pyRepr(s) {
|
|
3523
|
-
const hasSingle = s.includes("'");
|
|
3524
|
-
const hasDouble = s.includes('"');
|
|
3525
|
-
const quote = hasSingle && !hasDouble ? '"' : "'";
|
|
3526
|
-
let out = quote;
|
|
3527
|
-
for (const ch of s) {
|
|
3528
|
-
if (ch === quote || ch === "\\") {
|
|
3529
|
-
out += `\\${ch}`;
|
|
3530
|
-
} else if (ch === "\n") {
|
|
3531
|
-
out += "\\n";
|
|
3532
|
-
} else if (ch === "\r") {
|
|
3533
|
-
out += "\\r";
|
|
3534
|
-
} else if (ch === " ") {
|
|
3535
|
-
out += "\\t";
|
|
3536
|
-
} else {
|
|
3537
|
-
out += ch;
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
return out + quote;
|
|
3541
|
-
}
|
|
3542
|
-
function _rglobYmlSorted(root) {
|
|
3543
|
-
const out = [];
|
|
3544
|
-
const walk = (dir) => {
|
|
3545
|
-
let entries;
|
|
3546
|
-
try {
|
|
3547
|
-
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
3548
|
-
} catch {
|
|
3549
|
-
return;
|
|
3550
|
-
}
|
|
3551
|
-
for (const ent of entries) {
|
|
3552
|
-
const full = path7.join(dir, ent.name);
|
|
3553
|
-
if (ent.name.endsWith(".yml")) {
|
|
3554
|
-
out.push(full);
|
|
3555
|
-
}
|
|
3556
|
-
if (ent.isDirectory() || ent.isSymbolicLink() && _isDir2(full)) {
|
|
3557
|
-
walk(full);
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
};
|
|
3561
|
-
walk(root);
|
|
3562
|
-
out.sort();
|
|
3563
|
-
return out;
|
|
3564
|
-
}
|
|
3565
|
-
function _globJsonlSorted(dir) {
|
|
3566
|
-
return _globSorted(dir, ".jsonl");
|
|
3567
|
-
}
|
|
3568
|
-
function _globMdSorted(dir) {
|
|
3569
|
-
return _globSorted(dir, ".md");
|
|
3570
|
-
}
|
|
3571
|
-
function _globSorted(dir, suffix) {
|
|
3572
|
-
let entries;
|
|
3573
|
-
try {
|
|
3574
|
-
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
3575
|
-
} catch {
|
|
3576
|
-
return [];
|
|
3577
|
-
}
|
|
3578
|
-
const out = entries.filter((e) => e.name.endsWith(suffix) && (e.isFile() || e.isSymbolicLink() && _isFile2(path7.join(dir, e.name)))).map((e) => path7.join(dir, e.name));
|
|
3579
|
-
out.sort();
|
|
3580
|
-
return out;
|
|
3581
|
-
}
|
|
3582
|
-
function _iterdirSorted(dir) {
|
|
3583
|
-
let names;
|
|
3584
|
-
try {
|
|
3585
|
-
names = fs7.readdirSync(dir);
|
|
3586
|
-
} catch {
|
|
3587
|
-
return [];
|
|
3588
|
-
}
|
|
3589
|
-
const out = names.map((name) => path7.join(dir, name));
|
|
3590
|
-
out.sort();
|
|
3591
|
-
return out;
|
|
3592
|
-
}
|
|
3593
|
-
function _stem(p) {
|
|
3594
|
-
const base = path7.basename(p);
|
|
3595
|
-
const dot = base.lastIndexOf(".");
|
|
3596
|
-
return dot <= 0 ? base : base.slice(0, dot);
|
|
3597
|
-
}
|
|
3598
|
-
function pyJsonDumps2(value, indent) {
|
|
3599
|
-
return _escapeNonAscii3(_dumpsIndent2(value, indent, 0));
|
|
3600
|
-
}
|
|
3601
|
-
function _dumpsIndent2(value, indent, depth) {
|
|
3602
|
-
const pad = " ".repeat(indent * (depth + 1));
|
|
3603
|
-
const closePad = " ".repeat(indent * depth);
|
|
3604
|
-
if (value === null || value === void 0) {
|
|
3605
|
-
return "null";
|
|
3606
|
-
}
|
|
3607
|
-
if (typeof value === "boolean") {
|
|
3608
|
-
return value ? "true" : "false";
|
|
3609
|
-
}
|
|
3610
|
-
if (typeof value === "number") {
|
|
3611
|
-
return _jsonNum2(value);
|
|
3612
|
-
}
|
|
3613
|
-
if (typeof value === "string") {
|
|
3614
|
-
return _jsonStrAscii2(value);
|
|
3615
|
-
}
|
|
3616
|
-
if (value instanceof PyTimestamp2) {
|
|
3617
|
-
return _jsonStrAscii2(value.pyStr);
|
|
3618
|
-
}
|
|
3619
|
-
if (value instanceof PyFloat3) {
|
|
3620
|
-
return _jsonFloat2(value.value);
|
|
3621
|
-
}
|
|
3622
|
-
if (Array.isArray(value)) {
|
|
3623
|
-
if (value.length === 0) {
|
|
3624
|
-
return "[]";
|
|
3625
|
-
}
|
|
3626
|
-
const items = value.map((v) => pad + _dumpsIndent2(v, indent, depth + 1));
|
|
3627
|
-
return `[
|
|
3628
|
-
${items.join(",\n")}
|
|
3629
|
-
${closePad}]`;
|
|
3630
|
-
}
|
|
3631
|
-
if (_isPlainObject4(value)) {
|
|
3632
|
-
const keys = Object.keys(value);
|
|
3633
|
-
if (keys.length === 0) {
|
|
3634
|
-
return "{}";
|
|
3635
|
-
}
|
|
3636
|
-
const items = keys.map((k) => `${pad}${_jsonStrAscii2(k)}: ${_dumpsIndent2(value[k], indent, depth + 1)}`);
|
|
3637
|
-
return `{
|
|
3638
|
-
${items.join(",\n")}
|
|
3639
|
-
${closePad}}`;
|
|
3640
|
-
}
|
|
3641
|
-
return _jsonStrAscii2(String(value));
|
|
3642
|
-
}
|
|
3643
|
-
function _jsonNum2(n) {
|
|
3644
|
-
if (!Number.isFinite(n)) {
|
|
3645
|
-
if (Number.isNaN(n)) {
|
|
3646
|
-
return "NaN";
|
|
3647
|
-
}
|
|
3648
|
-
return n > 0 ? "Infinity" : "-Infinity";
|
|
3649
|
-
}
|
|
3650
|
-
return String(n);
|
|
3651
|
-
}
|
|
3652
|
-
function _jsonFloat2(n) {
|
|
3653
|
-
if (!Number.isFinite(n)) {
|
|
3654
|
-
if (Number.isNaN(n)) {
|
|
3655
|
-
return "NaN";
|
|
3656
|
-
}
|
|
3657
|
-
return n > 0 ? "Infinity" : "-Infinity";
|
|
3658
|
-
}
|
|
3659
|
-
return Number.isInteger(n) ? `${n}.0` : String(n);
|
|
3660
|
-
}
|
|
3661
|
-
function _jsonStrAscii2(s) {
|
|
3662
|
-
let out = '"';
|
|
3663
|
-
for (const ch of s) {
|
|
3664
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
3665
|
-
switch (ch) {
|
|
3666
|
-
case '"':
|
|
3667
|
-
out += '\\"';
|
|
3668
|
-
break;
|
|
3669
|
-
case "\\":
|
|
3670
|
-
out += "\\\\";
|
|
3671
|
-
break;
|
|
3672
|
-
case "\n":
|
|
3673
|
-
out += "\\n";
|
|
3674
|
-
break;
|
|
3675
|
-
case "\r":
|
|
3676
|
-
out += "\\r";
|
|
3677
|
-
break;
|
|
3678
|
-
case " ":
|
|
3679
|
-
out += "\\t";
|
|
3680
|
-
break;
|
|
3681
|
-
case "\b":
|
|
3682
|
-
out += "\\b";
|
|
3683
|
-
break;
|
|
3684
|
-
case "\f":
|
|
3685
|
-
out += "\\f";
|
|
3686
|
-
break;
|
|
3687
|
-
default:
|
|
3688
|
-
if (code < 32) {
|
|
3689
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
3690
|
-
} else {
|
|
3691
|
-
out += ch;
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
}
|
|
3695
|
-
return `${out}"`;
|
|
3696
|
-
}
|
|
3697
|
-
function _escapeNonAscii3(s) {
|
|
3698
|
-
let out = "";
|
|
3699
|
-
for (const ch of s) {
|
|
3700
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
3701
|
-
if (code > 127) {
|
|
3702
|
-
for (let i = 0; i < ch.length; i += 1) {
|
|
3703
|
-
out += `\\u${ch.charCodeAt(i).toString(16).padStart(4, "0")}`;
|
|
3704
|
-
}
|
|
3705
|
-
} else {
|
|
3706
|
-
out += ch;
|
|
3707
|
-
}
|
|
3708
|
-
}
|
|
3709
|
-
return out;
|
|
3710
|
-
}
|
|
3711
|
-
function _isCliEntry4() {
|
|
3712
|
-
if (process5.argv[1] === void 0) {
|
|
3713
|
-
return false;
|
|
3714
|
-
}
|
|
3715
|
-
const argvUrl = pathToFileURL4(path7.resolve(process5.argv[1])).href;
|
|
3716
|
-
if (import.meta.url === argvUrl) {
|
|
3717
|
-
return true;
|
|
3718
|
-
}
|
|
3719
|
-
try {
|
|
3720
|
-
const here = fs7.realpathSync(fileURLToPath6(import.meta.url));
|
|
3721
|
-
const argv = fs7.realpathSync(path7.resolve(process5.argv[1]));
|
|
3722
|
-
return here === argv;
|
|
3723
|
-
} catch {
|
|
3724
|
-
return false;
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
|
-
var _isMain3 = _isCliEntry4();
|
|
3728
|
-
if (_isMain3) {
|
|
3729
|
-
process5.exit(main5());
|
|
3730
|
-
}
|
|
4
|
+
memory_get_v1
|
|
5
|
+
} from "./chunk-XGA2NTU4.js";
|
|
6
|
+
import "./chunk-OY53J6II.js";
|
|
7
|
+
import "./chunk-J55WI3RR.js";
|
|
3731
8
|
|
|
3732
9
|
// src/scripts/_cli/cmd_memory_get.ts
|
|
3733
|
-
|
|
3734
|
-
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
|
+
var PROG = "agent-config memory:get";
|
|
15
|
+
var USAGE = `usage: ${PROG} <id> [<id> ...] [--format text|json]
|
|
3735
16
|
`;
|
|
3736
17
|
var ArgparseExit = class extends Error {
|
|
3737
18
|
constructor(code) {
|
|
@@ -3740,9 +21,9 @@ var ArgparseExit = class extends Error {
|
|
|
3740
21
|
}
|
|
3741
22
|
code;
|
|
3742
23
|
};
|
|
3743
|
-
function
|
|
3744
|
-
|
|
3745
|
-
|
|
24
|
+
function _argError(msg) {
|
|
25
|
+
process.stderr.write(USAGE);
|
|
26
|
+
process.stderr.write(`${PROG}: error: ${msg}
|
|
3746
27
|
`);
|
|
3747
28
|
throw new ArgparseExit(2);
|
|
3748
29
|
}
|
|
@@ -3752,7 +33,7 @@ function _parse(argv) {
|
|
|
3752
33
|
while (i < argv.length) {
|
|
3753
34
|
const a = argv[i];
|
|
3754
35
|
if (a === "-h" || a === "--help") {
|
|
3755
|
-
|
|
36
|
+
process.stdout.write(
|
|
3756
37
|
`${USAGE}
|
|
3757
38
|
Batch-fetch FULL memory entries by id \u2014 the second half of the
|
|
3758
39
|
index-first retrieval workflow (\`memory:lookup --detail index\`
|
|
@@ -3771,28 +52,28 @@ exit codes:
|
|
|
3771
52
|
throw new ArgparseExit(0);
|
|
3772
53
|
} else if (a === "--format") {
|
|
3773
54
|
const val = argv[i + 1];
|
|
3774
|
-
if (val === void 0)
|
|
55
|
+
if (val === void 0) _argError("argument --format: expected one argument");
|
|
3775
56
|
if (val !== "text" && val !== "json") {
|
|
3776
|
-
|
|
57
|
+
_argError(`argument --format: invalid choice: '${val}' (choose from 'text', 'json')`);
|
|
3777
58
|
}
|
|
3778
59
|
opts.format = val;
|
|
3779
60
|
i += 2;
|
|
3780
61
|
} else if (a.startsWith("--format=")) {
|
|
3781
62
|
const val = a.slice("--format=".length);
|
|
3782
63
|
if (val !== "text" && val !== "json") {
|
|
3783
|
-
|
|
64
|
+
_argError(`argument --format: invalid choice: '${val}' (choose from 'text', 'json')`);
|
|
3784
65
|
}
|
|
3785
66
|
opts.format = val;
|
|
3786
67
|
i += 1;
|
|
3787
68
|
} else if (a.startsWith("--")) {
|
|
3788
|
-
|
|
69
|
+
_argError(`unrecognized arguments: ${a}`);
|
|
3789
70
|
} else {
|
|
3790
71
|
opts.ids.push(a);
|
|
3791
72
|
i += 1;
|
|
3792
73
|
}
|
|
3793
74
|
}
|
|
3794
75
|
if (opts.ids.length === 0) {
|
|
3795
|
-
|
|
76
|
+
_argError("at least one <id> is required");
|
|
3796
77
|
}
|
|
3797
78
|
return opts;
|
|
3798
79
|
}
|
|
@@ -3819,54 +100,54 @@ function _renderValue(value) {
|
|
|
3819
100
|
}
|
|
3820
101
|
return JSON.stringify(value);
|
|
3821
102
|
}
|
|
3822
|
-
function
|
|
3823
|
-
const opts = _parse(argv ??
|
|
103
|
+
function main(argv = null) {
|
|
104
|
+
const opts = _parse(argv ?? process.argv.slice(2));
|
|
3824
105
|
const envelope = memory_get_v1(opts.ids);
|
|
3825
106
|
const entries = Array.isArray(envelope["entries"]) ? envelope["entries"] : [];
|
|
3826
107
|
const idStatus = isPlainObject(envelope["ids"]) ? envelope["ids"] : {};
|
|
3827
108
|
const unknown = opts.ids.filter((id) => idStatus[id] === "unknown");
|
|
3828
109
|
if (opts.format === "json") {
|
|
3829
|
-
|
|
110
|
+
process.stdout.write(`${JSON.stringify(envelope, null, 2)}
|
|
3830
111
|
`);
|
|
3831
112
|
} else {
|
|
3832
113
|
if (entries.length === 0) {
|
|
3833
|
-
|
|
114
|
+
process.stdout.write(" (no entries found)\n");
|
|
3834
115
|
} else {
|
|
3835
116
|
for (const entry of entries) {
|
|
3836
|
-
|
|
117
|
+
process.stdout.write(`${_renderEntryText(entry)}
|
|
3837
118
|
`);
|
|
3838
119
|
}
|
|
3839
120
|
}
|
|
3840
121
|
for (const id of unknown) {
|
|
3841
|
-
|
|
122
|
+
process.stderr.write(`${PROG}: unknown id: ${id}
|
|
3842
123
|
`);
|
|
3843
124
|
}
|
|
3844
125
|
}
|
|
3845
126
|
return unknown.length > 0 ? 1 : 0;
|
|
3846
127
|
}
|
|
3847
|
-
var
|
|
3848
|
-
function
|
|
3849
|
-
if (
|
|
128
|
+
var _HERE = fileURLToPath(import.meta.url);
|
|
129
|
+
function _isCliEntry() {
|
|
130
|
+
if (process.argv[1] === void 0) {
|
|
3850
131
|
return false;
|
|
3851
132
|
}
|
|
3852
|
-
const argvUrl =
|
|
133
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
3853
134
|
if (import.meta.url === argvUrl) {
|
|
3854
135
|
return true;
|
|
3855
136
|
}
|
|
3856
137
|
try {
|
|
3857
|
-
const here =
|
|
3858
|
-
const argv =
|
|
138
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
139
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
3859
140
|
return here === argv;
|
|
3860
141
|
} catch {
|
|
3861
142
|
return false;
|
|
3862
143
|
}
|
|
3863
144
|
}
|
|
3864
|
-
if (
|
|
145
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
3865
146
|
try {
|
|
3866
|
-
|
|
147
|
+
process.exitCode = main(process.argv.slice(2));
|
|
3867
148
|
} catch (exc) {
|
|
3868
149
|
if (exc instanceof ArgparseExit) {
|
|
3869
|
-
|
|
150
|
+
process.exitCode = exc.code;
|
|
3870
151
|
} else {
|
|
3871
152
|
throw exc;
|
|
3872
153
|
}
|
|
@@ -3874,5 +155,5 @@ if (_isCliEntry5() || process6.argv[1] === _HERE2) {
|
|
|
3874
155
|
}
|
|
3875
156
|
export {
|
|
3876
157
|
ArgparseExit,
|
|
3877
|
-
|
|
158
|
+
main
|
|
3878
159
|
};
|