@fusengine/harness 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/index.mjs +2 -2
- package/dist/{cache-BzbX-ztL.mjs → cache-C9z9LclL.mjs} +1 -31
- package/dist/cli/bin.mjs +14 -3
- package/dist/{skill-triggers-BZxov1es.mjs → describe-BYqhoV4c.mjs} +363 -2
- package/dist/freshness/index.mjs +1 -1
- package/dist/{freshness-CezohJHo.mjs → freshness-otdUpuvP.mjs} +1 -1
- package/dist/handle-DW9cWdVt.mjs +3356 -0
- package/dist/{index-BqdCjaT9.d.mts → index-mISsk0ff.d.mts} +143 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +6 -6
- package/dist/{json-io-xpTDuvtn.mjs → json-io-CAn72gI4.mjs} +1 -1
- package/dist/policy/index.d.mts +2 -2
- package/dist/policy/index.mjs +2 -2
- package/dist/runtime/index.d.mts +431 -1
- package/dist/runtime/index.mjs +2 -2
- package/dist/state/index.mjs +1 -1
- package/dist/{state-Cs0Y0MG_.mjs → state-ByhLeKyD.mjs} +1 -1
- package/dist/{store-BnHpq2ZB.mjs → store-D-ge2ZPI.mjs} +1 -1
- package/dist/{store-DeIsfMg5.mjs → store-PrNPm6So.mjs} +30 -1
- package/dist/tracking/index.mjs +1 -1
- package/package.json +10 -3
- package/dist/handle-nu3GYVek.mjs +0 -1090
package/dist/cache/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as extractText, i as mcpCacheKey, n as cachePath, r as cacheStore, t as cacheLookup } from "../store-
|
|
1
|
+
import { n as jaccardSimilar, r as queryHash, t as compactMarkdown } from "../cache-C9z9LclL.mjs";
|
|
2
|
+
import { a as extractText, i as mcpCacheKey, n as cachePath, o as loadIndex, r as cacheStore, s as summarizeIndex, t as cacheLookup } from "../store-PrNPm6So.mjs";
|
|
3
3
|
export { cacheLookup, cachePath, cacheStore, compactMarkdown, extractText, jaccardSimilar, loadIndex, mcpCacheKey, queryHash, summarizeIndex };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
//#region src/cache/compact.ts
|
|
4
3
|
const HTML_ENTITIES = {
|
|
@@ -54,33 +53,4 @@ function jaccardSimilar(a, b, threshold = .8) {
|
|
|
54
53
|
return inter / union > threshold;
|
|
55
54
|
}
|
|
56
55
|
//#endregion
|
|
57
|
-
|
|
58
|
-
/** Read a JSON array from `path`; [] on missing/corrupt/non-array. */
|
|
59
|
-
function loadIndex(path) {
|
|
60
|
-
try {
|
|
61
|
-
if (!existsSync(path)) return [];
|
|
62
|
-
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
63
|
-
return Array.isArray(data) ? data : [];
|
|
64
|
-
} catch {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/** Summarize an index of `{ tool?, ts? }` entries. */
|
|
69
|
-
function summarizeIndex(index) {
|
|
70
|
-
const byTool = {};
|
|
71
|
-
const timestamps = [];
|
|
72
|
-
for (const entry of index) {
|
|
73
|
-
if (typeof entry !== "object" || entry === null) continue;
|
|
74
|
-
const e = entry;
|
|
75
|
-
if (typeof e.tool === "string") byTool[e.tool] = (byTool[e.tool] ?? 0) + 1;
|
|
76
|
-
if (typeof e.ts === "string") timestamps.push(e.ts);
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
total: index.length,
|
|
80
|
-
byTool,
|
|
81
|
-
oldestTs: timestamps.length ? timestamps.reduce((a, b) => a < b ? a : b) : null,
|
|
82
|
-
newestTs: timestamps.length ? timestamps.reduce((a, b) => a > b ? a : b) : null
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
export { queryHash as a, jaccardSimilar as i, summarizeIndex as n, compactMarkdown as r, loadIndex as t };
|
|
56
|
+
export { jaccardSimilar as n, queryHash as r, compactMarkdown as t };
|
package/dist/cli/bin.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { r as resolveTtlSec } from "../ttl-BG55s6HZ.mjs";
|
|
|
3
3
|
import { t as detectHarness } from "../harness-C8Nxxyn_.mjs";
|
|
4
4
|
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-CXsV-wIJ.mjs";
|
|
5
5
|
import { n as writeInitFile, t as initFor } from "../run-D91N4ul1.mjs";
|
|
6
|
-
import { t as handleHook } from "../handle-
|
|
6
|
+
import { t as handleHook } from "../handle-DW9cWdVt.mjs";
|
|
7
7
|
//#region src/cli/bin.ts
|
|
8
8
|
/**
|
|
9
9
|
* harness — CLI for @fusengine/harness.
|
|
@@ -25,11 +25,22 @@ async function readStdin() {
|
|
|
25
25
|
}
|
|
26
26
|
const cmd = process.argv[2];
|
|
27
27
|
if (cmd === "hook") {
|
|
28
|
-
const
|
|
28
|
+
const id = process.argv[3] ?? detectHarness().id;
|
|
29
|
+
const scopeArg = process.argv[4];
|
|
30
|
+
const scope = scopeArg !== void 0 && (/* @__PURE__ */ new Set([
|
|
31
|
+
"solid",
|
|
32
|
+
"rules",
|
|
33
|
+
"carto",
|
|
34
|
+
"security",
|
|
35
|
+
"changelog",
|
|
36
|
+
"aipilot"
|
|
37
|
+
])).has(scopeArg) ? scopeArg : "core";
|
|
38
|
+
const outcome = await handleHook(id, await readStdin(), {
|
|
29
39
|
now: Date.now(),
|
|
30
40
|
cwd: process.cwd(),
|
|
31
41
|
refsDir: process.env.FUSE_HARNESS_REFS,
|
|
32
|
-
windowMs: resolveTtlSec(process.env) * 1e3
|
|
42
|
+
windowMs: resolveTtlSec(process.env) * 1e3,
|
|
43
|
+
scope
|
|
33
44
|
});
|
|
34
45
|
if (outcome.stdout) process.stdout.write(outcome.stdout);
|
|
35
46
|
process.exit(outcome.exit);
|
|
@@ -3,7 +3,8 @@ import { k as countLines } from "./evaluate-j3gRJ_ng.mjs";
|
|
|
3
3
|
import { r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-Dd_x1-tZ.mjs";
|
|
4
4
|
import { t as routeReferences } from "./router-D8cVrI-s.mjs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
6
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
7
8
|
//#region src/policy/detect-project.ts
|
|
8
9
|
/** Keywords that signal a development task (APEX trigger). */
|
|
9
10
|
const DEV_KEYWORDS = /\b(implement|create|build|fix|add|refactor|develop|feature|bug|update|modify|change|write|code)\b/i;
|
|
@@ -673,4 +674,364 @@ function skillTriggerGate(framework, content, refsRead, forcedSkill, cwd) {
|
|
|
673
674
|
};
|
|
674
675
|
}
|
|
675
676
|
//#endregion
|
|
676
|
-
|
|
677
|
+
//#region src/policy/claude-md-context.ts
|
|
678
|
+
/** Dev-verb regex (FR/EN) that triggers the APEX preamble (case-insensitive). */
|
|
679
|
+
const DEV_VERBS = /(cr[ée]er|impl[ée]menter|ajouter|d[ée]velopper|construire|build|refactor|migrer|implement|create|add|develop)/i;
|
|
680
|
+
/**
|
|
681
|
+
* Detect the project type from the cwd, reproducing the legacy Python logic:
|
|
682
|
+
* package.json containing "next" → nextjs, else "react" → react; else
|
|
683
|
+
* composer.json+artisan → laravel; else Package.swift / *.xcodeproj → swift;
|
|
684
|
+
* else generic.
|
|
685
|
+
* @param cwd - Project root to scan.
|
|
686
|
+
* @returns The detected project type label.
|
|
687
|
+
*/
|
|
688
|
+
function detectClaudeMdProjectType(cwd) {
|
|
689
|
+
const pkg = join(cwd, "package.json");
|
|
690
|
+
if (existsSync(pkg)) try {
|
|
691
|
+
const content = readFileSync(pkg, "utf-8");
|
|
692
|
+
if (content.includes("next")) return "nextjs";
|
|
693
|
+
if (content.includes("react")) return "react";
|
|
694
|
+
} catch {}
|
|
695
|
+
if (existsSync(join(cwd, "composer.json")) && existsSync(join(cwd, "artisan"))) return "laravel";
|
|
696
|
+
if (existsSync(join(cwd, "Package.swift"))) return "swift";
|
|
697
|
+
try {
|
|
698
|
+
if (readdirSync(cwd).some((f) => f.endsWith(".xcodeproj"))) return "swift";
|
|
699
|
+
} catch {}
|
|
700
|
+
return "generic";
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Build the APEX instruction preamble for a development task.
|
|
704
|
+
* @param projectType - Detected project type label.
|
|
705
|
+
* @param maxLines - SOLID per-file line ceiling.
|
|
706
|
+
* @returns The APEX instruction text.
|
|
707
|
+
*/
|
|
708
|
+
function buildApexInstruction(projectType, maxLines) {
|
|
709
|
+
return `INSTRUCTION: This is a development task. Use APEX methodology:
|
|
710
|
+
|
|
711
|
+
**TRACKING FILE**: [project]/.claude/apex/task.json (auto-created on first Write/Edit)
|
|
712
|
+
|
|
713
|
+
1. **ANALYZE** (MANDATORY - 3 AGENTS IN PARALLEL):
|
|
714
|
+
- explore-codebase + research-expert + ${projectType}-expert (framework expertise)\n - Project type detected: ${projectType}\n\n2. **PLAN**: Use TaskCreate to break down tasks (<${maxLines} lines per file)\n\n3. **EXECUTE**: ${projectType}-expert, follow SOLID principles, split at ${maxLines - 10} lines\n\n4. **EXAMINE**: Run sniper agent after ANY modification\n\n**IMPORTANT**: Read .claude/apex/task.json to check documentation status before writing code.`;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Build the UserPromptSubmit injection text: read `~/.claude/CLAUDE.md` and,
|
|
718
|
+
* when the prompt matches a dev verb, prepend the APEX instruction. Returns
|
|
719
|
+
* `null` when CLAUDE.md is absent/unreadable (the hook then emits nothing).
|
|
720
|
+
* @param prompt - The raw user prompt.
|
|
721
|
+
* @param cwd - Project root (for project-type detection).
|
|
722
|
+
* @returns The injection text, or `null` to emit nothing.
|
|
723
|
+
*/
|
|
724
|
+
function buildClaudeMdContext(prompt, cwd) {
|
|
725
|
+
const claudeMd = join(homedir(), ".claude", "CLAUDE.md");
|
|
726
|
+
if (!existsSync(claudeMd)) return null;
|
|
727
|
+
let claudeContent;
|
|
728
|
+
try {
|
|
729
|
+
claudeContent = readFileSync(claudeMd, "utf-8");
|
|
730
|
+
} catch {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
if (!DEV_VERBS.test(prompt)) return `# CLAUDE.md\n${claudeContent}`;
|
|
734
|
+
return `${buildApexInstruction(detectClaudeMdProjectType(cwd), resolveMaxLines())}\n\n# CLAUDE.md\n${claudeContent}`;
|
|
735
|
+
}
|
|
736
|
+
//#endregion
|
|
737
|
+
//#region src/policy/apex-task-context.ts
|
|
738
|
+
/**
|
|
739
|
+
* Read the current task state from `task.json`, reproducing the legacy Python
|
|
740
|
+
* logic. Any read/parse error falls back to `("1", "", "analyze", "none")`.
|
|
741
|
+
* @param taskFile - Absolute path to `.claude/apex/task.json`.
|
|
742
|
+
* @returns The parsed {@link ApexTaskState}.
|
|
743
|
+
*/
|
|
744
|
+
function loadApexTaskState(taskFile) {
|
|
745
|
+
try {
|
|
746
|
+
const data = JSON.parse(readFileSync(taskFile, "utf-8"));
|
|
747
|
+
const id = String(data.current_task ?? "1");
|
|
748
|
+
const task = (data.tasks ?? {})[id] ?? {};
|
|
749
|
+
const subject = typeof task.subject === "string" ? task.subject : "";
|
|
750
|
+
const phase = typeof task.phase === "string" ? task.phase : "analyze";
|
|
751
|
+
const consultedMap = task.doc_consulted ?? {};
|
|
752
|
+
return {
|
|
753
|
+
id,
|
|
754
|
+
subject,
|
|
755
|
+
phase,
|
|
756
|
+
docs: Object.entries(consultedMap).filter(([, v]) => typeof v === "object" && v !== null && v.consulted === true).map(([k]) => k).join(", ") || "none"
|
|
757
|
+
};
|
|
758
|
+
} catch {
|
|
759
|
+
return {
|
|
760
|
+
id: "1",
|
|
761
|
+
subject: "",
|
|
762
|
+
phase: "analyze",
|
|
763
|
+
docs: "none"
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Build the APEX context string injected into a Task sub-agent prompt.
|
|
769
|
+
* @param state - The parsed task state.
|
|
770
|
+
* @param maxLines - SOLID per-file line ceiling.
|
|
771
|
+
* @returns The injection text.
|
|
772
|
+
*/
|
|
773
|
+
function buildApexTaskContext(state, maxLines) {
|
|
774
|
+
return `⚠️ APEX MODE - Read .claude/apex/AGENTS.md for rules\n\nCurrent: Task #${state.id} - ${state.subject} (Phase: ${state.phase})\nDocs consulted: ${state.docs}\n\nAgent must:\n1. Read task.json → find last 3 completed tasks\n2. Read their notes in docs/ (task-{ID}-{subject}.md)\n3. TaskList → see pending tasks\n4. TaskUpdate(in_progress) → before starting\n5. Apply SOLID (files < ${maxLines} lines)\n6. Write notes to docs/task-{ID}-{subject}.md\n7. TaskUpdate(completed) → triggers auto-commit`;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Build the PreToolUse Task injection, gated on the existence of the project's
|
|
778
|
+
* `.claude/apex/` directory. Returns `null` when APEX is not active (no dir).
|
|
779
|
+
* @param projectRoot - `CLAUDE_PROJECT_DIR` or cwd.
|
|
780
|
+
* @returns The injection text, or `null` to emit nothing.
|
|
781
|
+
*/
|
|
782
|
+
function buildApexTaskInjection(projectRoot) {
|
|
783
|
+
const apexDir = join(projectRoot, ".claude", "apex");
|
|
784
|
+
if (!existsSync(apexDir)) return null;
|
|
785
|
+
return buildApexTaskContext(loadApexTaskState(join(apexDir, "task.json")), resolveMaxLines());
|
|
786
|
+
}
|
|
787
|
+
//#endregion
|
|
788
|
+
//#region src/policy/cartographer/indicators.ts
|
|
789
|
+
/**
|
|
790
|
+
* Cartographer indicators — pure data sets used to detect a project root and to
|
|
791
|
+
* exclude noise directories when walking a tree. Ports the constant tables from
|
|
792
|
+
* `generate_project_map.py` / `write_recursive.py`.
|
|
793
|
+
*/
|
|
794
|
+
/** Filenames whose presence marks a directory as a project root. */
|
|
795
|
+
const PROJECT_INDICATORS = /* @__PURE__ */ new Set([
|
|
796
|
+
"package.json",
|
|
797
|
+
"deno.json",
|
|
798
|
+
"bun.lockb",
|
|
799
|
+
"bun.lock",
|
|
800
|
+
"tsconfig.json",
|
|
801
|
+
"package-lock.json",
|
|
802
|
+
"yarn.lock",
|
|
803
|
+
"pnpm-lock.yaml",
|
|
804
|
+
"pnpm-workspace.yaml",
|
|
805
|
+
"composer.json",
|
|
806
|
+
"artisan",
|
|
807
|
+
"Cargo.toml",
|
|
808
|
+
"rust-toolchain.toml",
|
|
809
|
+
"go.mod",
|
|
810
|
+
"pyproject.toml",
|
|
811
|
+
"setup.py",
|
|
812
|
+
"setup.cfg",
|
|
813
|
+
"Pipfile",
|
|
814
|
+
"requirements.txt",
|
|
815
|
+
"environment.yml",
|
|
816
|
+
"Gemfile",
|
|
817
|
+
"Package.swift",
|
|
818
|
+
"Podfile",
|
|
819
|
+
"pubspec.yaml",
|
|
820
|
+
"pom.xml",
|
|
821
|
+
"build.gradle",
|
|
822
|
+
"build.gradle.kts",
|
|
823
|
+
"settings.gradle",
|
|
824
|
+
"build.sbt",
|
|
825
|
+
"Makefile",
|
|
826
|
+
"CMakeLists.txt",
|
|
827
|
+
"meson.build",
|
|
828
|
+
"configure.ac",
|
|
829
|
+
"Directory.Build.props",
|
|
830
|
+
"global.json",
|
|
831
|
+
"mix.exs",
|
|
832
|
+
"rebar.config",
|
|
833
|
+
"project.clj",
|
|
834
|
+
"deps.edn",
|
|
835
|
+
"stack.yaml",
|
|
836
|
+
"cabal.project",
|
|
837
|
+
"dune-project",
|
|
838
|
+
"build.zig",
|
|
839
|
+
"gleam.toml",
|
|
840
|
+
"v.mod",
|
|
841
|
+
"Project.toml",
|
|
842
|
+
"DESCRIPTION",
|
|
843
|
+
"cpanfile",
|
|
844
|
+
"Makefile.PL",
|
|
845
|
+
".luacheckrc",
|
|
846
|
+
"astro.config.mjs",
|
|
847
|
+
"next.config.js",
|
|
848
|
+
"next.config.mjs",
|
|
849
|
+
"nuxt.config.ts",
|
|
850
|
+
"vite.config.ts",
|
|
851
|
+
"next.config.ts",
|
|
852
|
+
"angular.json",
|
|
853
|
+
"svelte.config.js",
|
|
854
|
+
"svelte.config.ts",
|
|
855
|
+
"main.tf",
|
|
856
|
+
"ansible.cfg",
|
|
857
|
+
"pulumi.yaml",
|
|
858
|
+
"cdk.json",
|
|
859
|
+
"Chart.yaml",
|
|
860
|
+
"wrangler.toml",
|
|
861
|
+
"fly.toml",
|
|
862
|
+
"turbo.json",
|
|
863
|
+
"nx.json",
|
|
864
|
+
"BUILD",
|
|
865
|
+
"WORKSPACE",
|
|
866
|
+
"Justfile",
|
|
867
|
+
"Taskfile.yml",
|
|
868
|
+
"docker-compose.yml",
|
|
869
|
+
"docker-compose.yaml",
|
|
870
|
+
"compose.yml",
|
|
871
|
+
"compose.yaml",
|
|
872
|
+
"Dockerfile",
|
|
873
|
+
".git"
|
|
874
|
+
]);
|
|
875
|
+
/** Directory names skipped entirely during the tree walk. */
|
|
876
|
+
const EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
877
|
+
"node_modules",
|
|
878
|
+
".git",
|
|
879
|
+
".next",
|
|
880
|
+
".nuxt",
|
|
881
|
+
"dist",
|
|
882
|
+
"build",
|
|
883
|
+
".output",
|
|
884
|
+
"vendor",
|
|
885
|
+
"__pycache__",
|
|
886
|
+
".venv",
|
|
887
|
+
"venv",
|
|
888
|
+
".cartographer",
|
|
889
|
+
".claude",
|
|
890
|
+
".ruff_cache",
|
|
891
|
+
".DS_Store",
|
|
892
|
+
"coverage",
|
|
893
|
+
".turbo",
|
|
894
|
+
".vercel",
|
|
895
|
+
".netlify",
|
|
896
|
+
"Pods",
|
|
897
|
+
"DerivedData",
|
|
898
|
+
".build",
|
|
899
|
+
".swiftpm"
|
|
900
|
+
]);
|
|
901
|
+
//#endregion
|
|
902
|
+
//#region src/policy/cartographer/frontmatter.ts
|
|
903
|
+
/**
|
|
904
|
+
* Frontmatter parsing — pure text helpers (no fs). Ports `parse_frontmatter.py`.
|
|
905
|
+
*/
|
|
906
|
+
const BLOCK_SCALARS = /* @__PURE__ */ new Set([
|
|
907
|
+
"|",
|
|
908
|
+
">",
|
|
909
|
+
"|+",
|
|
910
|
+
"|-",
|
|
911
|
+
">+",
|
|
912
|
+
">-"
|
|
913
|
+
]);
|
|
914
|
+
/** Escape regex metacharacters in an arbitrary field name. */
|
|
915
|
+
function escapeRe(s) {
|
|
916
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Extract a single frontmatter field's value from `text`. Strips surrounding
|
|
920
|
+
* quotes; skips YAML block-scalar markers. Returns "" when absent.
|
|
921
|
+
* @param text - The full document text.
|
|
922
|
+
* @param field - The frontmatter key to read.
|
|
923
|
+
* @returns The field value, or "".
|
|
924
|
+
*/
|
|
925
|
+
function parseField(text, field) {
|
|
926
|
+
const fm = /^---\s*\n([\s\S]*?)\n---/.exec(text);
|
|
927
|
+
if (!fm || fm[1] === void 0) return "";
|
|
928
|
+
const lineRe = new RegExp(`^${escapeRe(field)}\\s*:\\s*(.+)$`);
|
|
929
|
+
for (const line of fm[1].split("\n")) {
|
|
930
|
+
const m = lineRe.exec(line);
|
|
931
|
+
if (!m || m[1] === void 0) continue;
|
|
932
|
+
const val = m[1].trim().replace(/^["']|["']$/g, "");
|
|
933
|
+
if (BLOCK_SCALARS.has(val)) continue;
|
|
934
|
+
return val;
|
|
935
|
+
}
|
|
936
|
+
return "";
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Derive a short description from the body following the frontmatter: the first
|
|
940
|
+
* non-empty trimmed line, sliced to `maxLen`. Returns "" when none.
|
|
941
|
+
* @param text - The full document text.
|
|
942
|
+
* @param maxLen - Maximum length of the returned description.
|
|
943
|
+
* @returns The body-derived description, or "".
|
|
944
|
+
*/
|
|
945
|
+
function parseBodyDesc(text, maxLen = 60) {
|
|
946
|
+
const m = /^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/.exec(text);
|
|
947
|
+
if (!m || m[1] === void 0) return "";
|
|
948
|
+
for (const raw of m[1].split("\n")) {
|
|
949
|
+
const line = raw.trim();
|
|
950
|
+
if (line) return line.slice(0, maxLen);
|
|
951
|
+
}
|
|
952
|
+
return "";
|
|
953
|
+
}
|
|
954
|
+
//#endregion
|
|
955
|
+
//#region src/policy/cartographer/entry.ts
|
|
956
|
+
const ENTRY_RE = /^(.*?)\[([^\]]+)\]\(([^)]+)\)\s*(?:—|-{1,2})\s*(.*)$/;
|
|
957
|
+
const ENRICH_RE = /^(?:.*?)\[([^\]]+)\]\(([^)]+)\)\s*(?:—|-{1,2})\s*(.+)$/;
|
|
958
|
+
/**
|
|
959
|
+
* Parse a `merge_index` tree line into its parts. Returns null on no match.
|
|
960
|
+
* @param line - The raw tree line.
|
|
961
|
+
* @returns The parsed entry, or null.
|
|
962
|
+
*/
|
|
963
|
+
function parseEntry(line) {
|
|
964
|
+
const m = ENTRY_RE.exec(line);
|
|
965
|
+
if (!m || m[1] === void 0 || m[2] === void 0 || m[3] === void 0 || m[4] === void 0) return null;
|
|
966
|
+
return {
|
|
967
|
+
prefix: m[1],
|
|
968
|
+
name: m[2],
|
|
969
|
+
path: m[3],
|
|
970
|
+
desc: m[4]
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Parse an enrichment line into `[path, desc]`, requiring a non-empty desc.
|
|
975
|
+
* @param line - The raw index line.
|
|
976
|
+
* @returns The `[path, desc]` pair, or null.
|
|
977
|
+
*/
|
|
978
|
+
function parseEnrichment(line) {
|
|
979
|
+
const m = ENRICH_RE.exec(line);
|
|
980
|
+
if (!m || m[2] === void 0 || m[3] === void 0) return null;
|
|
981
|
+
const desc = m[3].trim();
|
|
982
|
+
return desc ? [m[2], desc] : null;
|
|
983
|
+
}
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/policy/cartographer/describe.ts
|
|
986
|
+
/**
|
|
987
|
+
* File-description heuristics — pure text in, description out (no fs). Ports
|
|
988
|
+
* `describe.py`.
|
|
989
|
+
*/
|
|
990
|
+
const SOURCE_SUFFIXES = /* @__PURE__ */ new Set([
|
|
991
|
+
".ts",
|
|
992
|
+
".tsx",
|
|
993
|
+
".js",
|
|
994
|
+
".jsx",
|
|
995
|
+
".py",
|
|
996
|
+
".swift"
|
|
997
|
+
]);
|
|
998
|
+
/**
|
|
999
|
+
* First `# ` Markdown heading text (sans hashes), sliced to 60. "" when none.
|
|
1000
|
+
* @param text - The document text.
|
|
1001
|
+
* @returns The heading text, or "".
|
|
1002
|
+
*/
|
|
1003
|
+
function firstHeading(text) {
|
|
1004
|
+
for (const line of text.split("\n")) if (line.startsWith("# ")) return line.replace(/^#+/, "").trim().slice(0, 60);
|
|
1005
|
+
return "";
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* First leading comment among the first 10 lines (`//`, `#` but not `#!`, or a
|
|
1009
|
+
* `"""`/`'''` docstring), sliced to 60. "" when none.
|
|
1010
|
+
* @param text - The source text.
|
|
1011
|
+
* @returns The comment text, or "".
|
|
1012
|
+
*/
|
|
1013
|
+
function firstComment(text) {
|
|
1014
|
+
const lines = text.split("\n").slice(0, 10);
|
|
1015
|
+
for (const raw of lines) {
|
|
1016
|
+
const line = raw.trim();
|
|
1017
|
+
if ((line.startsWith("//") || line.startsWith("#")) && !line.startsWith("#!")) return line.replace(/^[/#! ]+/, "").slice(0, 60);
|
|
1018
|
+
if (line.startsWith("\"\"\"") || line.startsWith("'''")) return line.replace(/^['"\s]+|['"\s]+$/g, "").slice(0, 60);
|
|
1019
|
+
}
|
|
1020
|
+
return "";
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Derive a description from a file's suffix + text. For `.md`, the supplied
|
|
1024
|
+
* frontmatter `description` (truncated) wins over the first heading; for known
|
|
1025
|
+
* source suffixes, the first comment; else "".
|
|
1026
|
+
* @param suffix - The file extension (with dot).
|
|
1027
|
+
* @param text - The file text.
|
|
1028
|
+
* @param mdField - The pre-parsed frontmatter `description` (md only).
|
|
1029
|
+
* @returns The derived description, or "".
|
|
1030
|
+
*/
|
|
1031
|
+
function descFromText(suffix, text, mdField) {
|
|
1032
|
+
if (suffix === ".md") return mdField.slice(0, 60) || firstHeading(text);
|
|
1033
|
+
if (SOURCE_SUFFIXES.has(suffix)) return firstComment(text);
|
|
1034
|
+
return "";
|
|
1035
|
+
}
|
|
1036
|
+
//#endregion
|
|
1037
|
+
export { solidReadGate as A, capVerbosity as C, docConsultedGate as D, brainstormGate as E, requiredArchSkill as F, detectModularArchitecture as M, detectProjectType as N, evaluateApex as O, isApexCommand as P, MAX_TOKENS as S, APEX_GATES as T, detectRequiredSkills as _, parseEntry as a, frameworkSolidGate as b, EXCLUDE_DIRS as c, buildApexTaskInjection as d, loadApexTaskState as f, detectClaudeMdProjectType as g, buildClaudeMdContext as h, parseEnrichment as i, DEV_KEYWORDS as j, freshnessGate as k, PROJECT_INDICATORS as l, buildApexInstruction as m, firstComment as n, parseBodyDesc as o, DEV_VERBS as p, firstHeading as r, parseField as s, descFromText as t, buildApexTaskContext as u, skillTriggerGate as v, detectCreationIntent as w, MAX_EXA_RESULTS as x, SKILL_TRIGGERS as y };
|
package/dist/freshness/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "../doc-helpers-Dd_x1-tZ.mjs";
|
|
2
|
-
import { t as incrementTrivialEditCounter } from "../freshness-
|
|
2
|
+
import { t as incrementTrivialEditCounter } from "../freshness-otdUpuvP.mjs";
|
|
3
3
|
export { formatDocDeny, formatDocSatisfactionStatus, incrementTrivialEditCounter, isDocConsulted, resolveSessions };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as writeJsonFile, n as ensureDir, r as readJsonFile } from "./json-io-CAn72gI4.mjs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
//#region src/freshness/trivial-edit-counter.ts
|
|
4
4
|
/**
|