@harness-engineering/cli 3.1.0 → 4.0.1
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/agents/personas/harness-pm.yaml +33 -0
- package/dist/agents/skills/claude-code/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/claude-code/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/claude-code/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +24 -26
- package/dist/agents/skills/claude-code/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/claude-code/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/claude-code/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/claude-code/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +8 -0
- package/dist/agents/skills/claude-code/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/claude-code/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/claude-code/product-advisor/SKILL.md +202 -0
- package/dist/agents/skills/claude-code/product-advisor/skill.yaml +74 -0
- package/dist/agents/skills/codex/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/codex/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/codex/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/codex/harness-execution/SKILL.md +24 -26
- package/dist/agents/skills/codex/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/codex/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/codex/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/codex/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/codex/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/codex/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +8 -0
- package/dist/agents/skills/codex/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/codex/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/codex/product-advisor/SKILL.md +202 -0
- package/dist/agents/skills/codex/product-advisor/skill.yaml +74 -0
- package/dist/agents/skills/cursor/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/cursor/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/cursor/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +24 -26
- package/dist/agents/skills/cursor/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/cursor/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/cursor/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/cursor/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +8 -0
- package/dist/agents/skills/cursor/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/cursor/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/cursor/product-advisor/SKILL.md +202 -0
- package/dist/agents/skills/cursor/product-advisor/skill.yaml +74 -0
- package/dist/agents/skills/gemini-cli/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/gemini-cli/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/gemini-cli/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +24 -26
- package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/gemini-cli/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/gemini-cli/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/gemini-cli/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +8 -0
- package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/gemini-cli/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/gemini-cli/product-advisor/SKILL.md +202 -0
- package/dist/agents/skills/gemini-cli/product-advisor/skill.yaml +74 -0
- package/dist/agents/skills/tests/interaction-channel.test.ts +64 -0
- package/dist/{agents-md-YYCLGMBF.js → agents-md-VJLYEH2S.js} +4 -4
- package/dist/{analyzer-V633GUHY-MBCQWKDU.js → analyzer-V633GUHY-OV335HGM.js} +2 -2
- package/dist/{architecture-5KPP4ID4.js → architecture-YQH2NYXY.js} +5 -5
- package/dist/{assess-project-B6ENHUAU.js → assess-project-KOTAPAHD.js} +1 -1
- package/dist/bin/harness-mcp.js +19 -18
- package/dist/bin/harness.js +31 -29
- package/dist/{check-phase-gate-REG3HWYC.js → check-phase-gate-CHU2E3OU.js} +6 -5
- package/dist/{chunk-UBTMPO5H.js → chunk-36GQREOE.js} +3 -3
- package/dist/{chunk-CLI4K2CH.js → chunk-4U5LDFCY.js} +1 -1
- package/dist/{chunk-IF3JK3G5.js → chunk-4VXFZSYF.js} +1 -1
- package/dist/{chunk-TE5YHHHZ.js → chunk-5RSXUAMN.js} +1 -1
- package/dist/{chunk-YF3AY6KS.js → chunk-6SCYTIF4.js} +6 -6
- package/dist/{chunk-N55WOGN3.js → chunk-6YFHUIGT.js} +1 -1
- package/dist/{chunk-RGQXHCCH.js → chunk-7PJCK7UE.js} +8 -8
- package/dist/{chunk-LMZ2JTNP.js → chunk-7TIJXWG3.js} +5 -9
- package/dist/chunk-AAUY53CC.js +61 -0
- package/dist/{chunk-XEOSSKIB.js → chunk-AX5KGRM6.js} +48 -4
- package/dist/{chunk-FUH4J43G.js → chunk-E5EW5HVJ.js} +2 -2
- package/dist/{chunk-C2ZGWMTN.js → chunk-EYMOPFSD.js} +2 -2
- package/dist/{chunk-FL5SLUKZ.js → chunk-GHBFNLV2.js} +2374 -1095
- package/dist/{chunk-HIPBU2Q4.js → chunk-I3CGCZJF.js} +9 -9
- package/dist/{chunk-OQFEFVIN.js → chunk-JPVOI7GP.js} +7 -7
- package/dist/{chunk-DNX35Q5K.js → chunk-M7F2PPN3.js} +3 -3
- package/dist/{chunk-A65QO5KN.js → chunk-MMLQPHZ5.js} +3032 -1166
- package/dist/{chunk-ERZCC5VH.js → chunk-OQ6KOQEV.js} +9 -9
- package/dist/{chunk-ZOUJP3EY.js → chunk-RG3TNIUB.js} +1659 -808
- package/dist/{chunk-PZX2Y3RV.js → chunk-SDMV4QHM.js} +5 -5
- package/dist/{chunk-XZ2Q4UTW.js → chunk-SEA2PFVI.js} +1 -1
- package/dist/{chunk-TCVQVHEF.js → chunk-T746REAE.js} +1 -1
- package/dist/{chunk-6B6UN6SG.js → chunk-UTJJJDNN.js} +11 -1
- package/dist/{chunk-BXKNWSG4.js → chunk-UWVMJEUJ.js} +1 -1
- package/dist/{chunk-WWACIIBA.js → chunk-V3ZQUCRH.js} +1 -1
- package/dist/{chunk-27AJKSQY.js → chunk-WBVCN35T.js} +1 -1
- package/dist/{chunk-PP6ZRL5T.js → chunk-WZJR6SOI.js} +536 -422
- package/dist/{chunk-YQAFKOOL.js → chunk-XAVMTAUT.js} +2 -2
- package/dist/chunk-Y6SUQC32.js +9 -0
- package/dist/{chunk-ZRKFDE6M.js → chunk-YANR2RL7.js} +1 -1
- package/dist/{chunk-2N4OENGE.js → chunk-YYFSFSB3.js} +1 -1
- package/dist/{ci-workflow-JEYYCBOC.js → ci-workflow-HO4WKGNG.js} +4 -4
- package/dist/{dist-3HW4LCHE.js → dist-6D2F2J57.js} +73 -17
- package/dist/{dist-OWHMNL4W.js → dist-DWBTZXR5.js} +1 -1
- package/dist/{docs-YRMW7UED.js → docs-FP47JONV.js} +5 -5
- package/dist/{engine-E7H5U3AC.js → engine-YUA6IYOG.js} +4 -4
- package/dist/{entropy-3JBKCGJJ.js → entropy-DBW2INZU.js} +4 -4
- package/dist/{feedback-U6VJ6AUJ.js → feedback-BPHADTRQ.js} +1 -1
- package/dist/{generate-agent-definitions-S2FRZF4Y.js → generate-agent-definitions-KPVGBGDY.js} +6 -6
- package/dist/{glob-helper-GLZAURJC.js → glob-helper-VR6UCZZB.js} +1 -1
- package/dist/{graph-loader-HHXN5FLP.js → graph-loader-STFGYKZH.js} +1 -1
- package/dist/hooks/adoption-tracker.js +6 -5
- package/dist/hooks/protect-config.js +15 -5
- package/dist/{index-builder-3AOQ3ZII.js → index-builder-52IJJHJC.js} +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.js +31 -29
- package/dist/{loader-RGQJYERV.js → loader-QOJQ6NZM.js} +4 -4
- package/dist/{mcp-NDTFZXWF.js → mcp-ZPX6O767.js} +21 -20
- package/dist/{performance-FPWKC4CN.js → performance-FXYTGJGQ.js} +5 -5
- package/dist/{review-pipeline-WUHG7PHO.js → review-pipeline-2TFQ5OAE.js} +4 -4
- package/dist/{runtime-Q3ORD7LW.js → runtime-XK6ZCHKE.js} +4 -4
- package/dist/{scan-P7YFKB77.js → scan-IYSVRPD2.js} +1 -1
- package/dist/{security-JDHTRBGC.js → security-4W2P5ZGI.js} +1 -1
- package/dist/{skill-executor-MOCUIAYS.js → skill-executor-BGUCAVBU.js} +2 -2
- package/dist/state-events-ECQAIZLU.js +25 -0
- package/dist/{validate-G3LKBOYA.js → validate-64KTEGIE.js} +5 -5
- package/dist/{validate-cross-check-4ITA72DF.js → validate-cross-check-J5PVGICB.js} +4 -4
- package/package.json +7 -7
|
@@ -1,22 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeDiffDefinition,
|
|
3
|
+
createSelfReviewDefinition,
|
|
4
|
+
handleAnalyzeDiff,
|
|
5
|
+
handleCreateSelfReview,
|
|
6
|
+
handleRequestPeerReview,
|
|
7
|
+
requestPeerReviewDefinition
|
|
8
|
+
} from "./chunk-OQ6KOQEV.js";
|
|
1
9
|
import {
|
|
2
10
|
getSecurityTrendsDefinition,
|
|
3
11
|
handleGetSecurityTrends,
|
|
4
12
|
handleRunSecurityScan,
|
|
5
13
|
runSecurityScanDefinition
|
|
6
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-M7F2PPN3.js";
|
|
7
15
|
import {
|
|
8
16
|
handleRunCodeReview,
|
|
9
17
|
runCodeReviewDefinition,
|
|
10
18
|
sortFindingsBySeverity
|
|
11
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EYMOPFSD.js";
|
|
12
20
|
import {
|
|
13
21
|
assessProjectDefinition,
|
|
14
22
|
handleAssessProject
|
|
15
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-6SCYTIF4.js";
|
|
24
|
+
import {
|
|
25
|
+
checkDocsDefinition,
|
|
26
|
+
handleCheckDocs
|
|
27
|
+
} from "./chunk-I3CGCZJF.js";
|
|
16
28
|
import {
|
|
17
29
|
detectEntropyDefinition,
|
|
18
30
|
handleDetectEntropy
|
|
19
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-SDMV4QHM.js";
|
|
20
32
|
import {
|
|
21
33
|
checkPerformanceDefinition,
|
|
22
34
|
getCriticalPathsDefinition,
|
|
@@ -26,18 +38,10 @@ import {
|
|
|
26
38
|
handleGetPerfBaselines,
|
|
27
39
|
handleUpdatePerfBaselines,
|
|
28
40
|
updatePerfBaselinesDefinition
|
|
29
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-7PJCK7UE.js";
|
|
30
42
|
import {
|
|
31
43
|
getBusinessKnowledgeResource
|
|
32
44
|
} from "./chunk-IOW3MW2K.js";
|
|
33
|
-
import {
|
|
34
|
-
analyzeDiffDefinition,
|
|
35
|
-
createSelfReviewDefinition,
|
|
36
|
-
handleAnalyzeDiff,
|
|
37
|
-
handleCreateSelfReview,
|
|
38
|
-
handleRequestPeerReview,
|
|
39
|
-
requestPeerReviewDefinition
|
|
40
|
-
} from "./chunk-ERZCC5VH.js";
|
|
41
45
|
import {
|
|
42
46
|
GENERATED_HEADER_CLAUDE,
|
|
43
47
|
GENERATED_HEADER_CODEX,
|
|
@@ -49,31 +53,27 @@ import {
|
|
|
49
53
|
} from "./chunk-3ISINLYT.js";
|
|
50
54
|
import {
|
|
51
55
|
loadGraphStore
|
|
52
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-4U5LDFCY.js";
|
|
53
57
|
import {
|
|
54
58
|
handleValidateProject,
|
|
55
59
|
validateToolDefinition
|
|
56
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-36GQREOE.js";
|
|
57
61
|
import {
|
|
58
62
|
checkDependenciesDefinition,
|
|
59
63
|
handleCheckDependencies
|
|
60
|
-
} from "./chunk-
|
|
61
|
-
import {
|
|
62
|
-
resolveProjectConfig
|
|
63
|
-
} from "./chunk-XZ2Q4UTW.js";
|
|
64
|
-
import {
|
|
65
|
-
checkDocsDefinition,
|
|
66
|
-
handleCheckDocs
|
|
67
|
-
} from "./chunk-HIPBU2Q4.js";
|
|
64
|
+
} from "./chunk-JPVOI7GP.js";
|
|
68
65
|
import {
|
|
69
66
|
resultToMcpResponse
|
|
70
67
|
} from "./chunk-RC3OI5NK.js";
|
|
68
|
+
import {
|
|
69
|
+
resolveProjectConfig
|
|
70
|
+
} from "./chunk-SEA2PFVI.js";
|
|
71
71
|
import {
|
|
72
72
|
sanitizePath
|
|
73
73
|
} from "./chunk-W6Y7ZW3Y.js";
|
|
74
74
|
import {
|
|
75
75
|
loadOrRebuildIndex
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-WBVCN35T.js";
|
|
77
77
|
import {
|
|
78
78
|
resolveCommunitySkillsDir,
|
|
79
79
|
resolveGlobalCommunitySkillsDir,
|
|
@@ -82,15 +82,18 @@ import {
|
|
|
82
82
|
resolveProjectSkillsDir,
|
|
83
83
|
resolveSkillsDir,
|
|
84
84
|
resolveTemplatesDir
|
|
85
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-UTJJJDNN.js";
|
|
86
86
|
import {
|
|
87
87
|
SkillMetadataSchema
|
|
88
88
|
} from "./chunk-HIK6OEUF.js";
|
|
89
|
+
import {
|
|
90
|
+
findFiles
|
|
91
|
+
} from "./chunk-Y6SUQC32.js";
|
|
89
92
|
import {
|
|
90
93
|
findConfigFile,
|
|
91
94
|
loadConfig,
|
|
92
95
|
resolveConfig
|
|
93
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-XAVMTAUT.js";
|
|
94
97
|
import {
|
|
95
98
|
logger
|
|
96
99
|
} from "./chunk-EBJQ6N4M.js";
|
|
@@ -104,8 +107,10 @@ import {
|
|
|
104
107
|
ConflictError,
|
|
105
108
|
DEFAULT_TOKEN_BUDGET,
|
|
106
109
|
DESTRUCTIVE_BASH,
|
|
110
|
+
SIGNAL_CATEGORY_MAP,
|
|
107
111
|
StructuralStrategy,
|
|
108
112
|
TruncationStrategy,
|
|
113
|
+
applyRoadmapDiff,
|
|
109
114
|
checkTaint,
|
|
110
115
|
computeLoadPlan,
|
|
111
116
|
createTrackerClient,
|
|
@@ -118,10 +123,13 @@ import {
|
|
|
118
123
|
loadTrackerClientConfigFromProject,
|
|
119
124
|
loadTrackerSyncConfig,
|
|
120
125
|
paginate,
|
|
126
|
+
reconcilePassed,
|
|
127
|
+
resolveRoadmapStore,
|
|
128
|
+
roadmapSourceExists,
|
|
121
129
|
scanForInjection,
|
|
122
130
|
serializeEnvelope,
|
|
123
131
|
writeTaint
|
|
124
|
-
} from "./chunk-
|
|
132
|
+
} from "./chunk-MMLQPHZ5.js";
|
|
125
133
|
import {
|
|
126
134
|
Err,
|
|
127
135
|
INDEXED_FILE_KINDS,
|
|
@@ -317,7 +325,6 @@ release-readiness.json
|
|
|
317
325
|
skills-index.json
|
|
318
326
|
stack-profile.json
|
|
319
327
|
metrics/
|
|
320
|
-
events.jsonl
|
|
321
328
|
.install-id
|
|
322
329
|
telemetry.json
|
|
323
330
|
.telemetry-notice-shown
|
|
@@ -738,7 +745,7 @@ async function handleInsightsSummary(input) {
|
|
|
738
745
|
const skip = Array.isArray(input.skip) ? input.skip.filter(
|
|
739
746
|
(k) => typeof k === "string" && INSIGHTS_KEYS.includes(k)
|
|
740
747
|
) : void 0;
|
|
741
|
-
const { composeInsights } = await import("./dist-
|
|
748
|
+
const { composeInsights } = await import("./dist-6D2F2J57.js");
|
|
742
749
|
const report = await composeInsights(projectPath, {
|
|
743
750
|
...skip && skip.length > 0 && { skip }
|
|
744
751
|
});
|
|
@@ -914,7 +921,7 @@ ${skippedMsg}`
|
|
|
914
921
|
async function handleInitProject(input) {
|
|
915
922
|
const i = input;
|
|
916
923
|
try {
|
|
917
|
-
const { TemplateEngine } = await import("./engine-
|
|
924
|
+
const { TemplateEngine } = await import("./engine-YUA6IYOG.js");
|
|
918
925
|
const engine = new TemplateEngine(resolveTemplatesDir());
|
|
919
926
|
const safePath = sanitizePath(i.path);
|
|
920
927
|
const detected = tryDetectFramework(engine, safePath, i);
|
|
@@ -936,7 +943,7 @@ var listPersonasDefinition = {
|
|
|
936
943
|
inputSchema: { type: "object", properties: {} }
|
|
937
944
|
};
|
|
938
945
|
async function handleListPersonas() {
|
|
939
|
-
const { listPersonas } = await import("./loader-
|
|
946
|
+
const { listPersonas } = await import("./loader-QOJQ6NZM.js");
|
|
940
947
|
const result = listPersonas(resolvePersonasDir());
|
|
941
948
|
return resultToMcpResponse(result);
|
|
942
949
|
}
|
|
@@ -960,10 +967,10 @@ async function handleGeneratePersonaArtifacts(input) {
|
|
|
960
967
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.name)) {
|
|
961
968
|
return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.name}`)));
|
|
962
969
|
}
|
|
963
|
-
const { loadPersona } = await import("./loader-
|
|
964
|
-
const { generateRuntime } = await import("./runtime-
|
|
965
|
-
const { generateAgentsMd } = await import("./agents-md-
|
|
966
|
-
const { generateCIWorkflow } = await import("./ci-workflow-
|
|
970
|
+
const { loadPersona } = await import("./loader-QOJQ6NZM.js");
|
|
971
|
+
const { generateRuntime } = await import("./runtime-XK6ZCHKE.js");
|
|
972
|
+
const { generateAgentsMd } = await import("./agents-md-VJLYEH2S.js");
|
|
973
|
+
const { generateCIWorkflow } = await import("./ci-workflow-HO4WKGNG.js");
|
|
967
974
|
const personasDir = resolvePersonasDir();
|
|
968
975
|
const filePath = path4.join(personasDir, `${input.name}.yaml`);
|
|
969
976
|
if (!filePath.startsWith(personasDir)) {
|
|
@@ -1018,9 +1025,9 @@ async function handleRunPersona(input) {
|
|
|
1018
1025
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.persona)) {
|
|
1019
1026
|
return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.persona}`)));
|
|
1020
1027
|
}
|
|
1021
|
-
const { loadPersona } = await import("./loader-
|
|
1028
|
+
const { loadPersona } = await import("./loader-QOJQ6NZM.js");
|
|
1022
1029
|
const { runPersona } = await import("./runner-AAEF2TXY.js");
|
|
1023
|
-
const { executeSkill } = await import("./skill-executor-
|
|
1030
|
+
const { executeSkill } = await import("./skill-executor-BGUCAVBU.js");
|
|
1024
1031
|
const personasDir = resolvePersonasDir();
|
|
1025
1032
|
const filePath = path4.join(personasDir, `${input.persona}.yaml`);
|
|
1026
1033
|
if (!filePath.startsWith(personasDir)) {
|
|
@@ -1460,11 +1467,16 @@ function loadOrGenerateProfile(projectRoot) {
|
|
|
1460
1467
|
return profile;
|
|
1461
1468
|
}
|
|
1462
1469
|
|
|
1463
|
-
// src/mcp/tools/
|
|
1470
|
+
// src/mcp/tools/skill-telemetry.ts
|
|
1471
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1472
|
+
import { join as join4 } from "path";
|
|
1473
|
+
var SKILL_EVENTS_FILE = join4(".harness", "metrics", "skill-events.jsonl");
|
|
1464
1474
|
async function emitSkillEvent(projectPath, event) {
|
|
1465
1475
|
try {
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1476
|
+
const metricsDir = join4(projectPath, ".harness", "metrics");
|
|
1477
|
+
mkdirSync2(metricsDir, { recursive: true });
|
|
1478
|
+
const record = { ...event, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1479
|
+
appendFileSync2(join4(metricsDir, "skill-events.jsonl"), JSON.stringify(record) + "\n");
|
|
1468
1480
|
} catch {
|
|
1469
1481
|
}
|
|
1470
1482
|
}
|
|
@@ -1536,9 +1548,10 @@ async function handleRunSkill(input) {
|
|
|
1536
1548
|
}
|
|
1537
1549
|
if (input.path) {
|
|
1538
1550
|
const projectPath = sanitizePath(input.path);
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
1551
|
+
const { readHarnessState, isEmptyHarnessState } = await import("./state-events-ECQAIZLU.js");
|
|
1552
|
+
const stateResult = await readHarnessState(projectPath);
|
|
1553
|
+
if (stateResult.ok && !isEmptyHarnessState(stateResult.value)) {
|
|
1554
|
+
const stateContent = JSON.stringify(stateResult.value, null, 2);
|
|
1542
1555
|
content += `
|
|
1543
1556
|
|
|
1544
1557
|
---
|
|
@@ -1733,25 +1746,25 @@ import * as fs10 from "fs";
|
|
|
1733
1746
|
import * as path11 from "path";
|
|
1734
1747
|
async function autoSyncRoadmap(projectPath) {
|
|
1735
1748
|
try {
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
const
|
|
1739
|
-
const
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
const
|
|
1743
|
-
const syncResult = syncRoadmap({ projectPath, roadmap });
|
|
1749
|
+
if (!roadmapSourceExists(projectPath)) return;
|
|
1750
|
+
const { syncRoadmap, applySyncChanges } = await import("./dist-6D2F2J57.js");
|
|
1751
|
+
const store = resolveRoadmapStore({ projectRoot: projectPath });
|
|
1752
|
+
const loaded = await store.load();
|
|
1753
|
+
if (!loaded.ok) return;
|
|
1754
|
+
const roadmap = loaded.value;
|
|
1755
|
+
const before = structuredClone(roadmap);
|
|
1756
|
+
const syncResult = await syncRoadmap({ projectPath, roadmap });
|
|
1744
1757
|
if (!syncResult.ok || syncResult.value.length === 0) {
|
|
1745
|
-
await triggerExternalSync(projectPath
|
|
1758
|
+
await triggerExternalSync(projectPath);
|
|
1746
1759
|
return;
|
|
1747
1760
|
}
|
|
1748
1761
|
applySyncChanges(roadmap, syncResult.value);
|
|
1749
|
-
|
|
1750
|
-
await triggerExternalSync(projectPath
|
|
1762
|
+
await applyRoadmapDiff(store, before, roadmap);
|
|
1763
|
+
await triggerExternalSync(projectPath);
|
|
1751
1764
|
} catch {
|
|
1752
1765
|
}
|
|
1753
1766
|
}
|
|
1754
|
-
async function triggerExternalSync(projectPath
|
|
1767
|
+
async function triggerExternalSync(projectPath) {
|
|
1755
1768
|
try {
|
|
1756
1769
|
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
1757
1770
|
if (!trackerConfig) return;
|
|
@@ -1765,12 +1778,12 @@ async function triggerExternalSync(projectPath, roadmapFile) {
|
|
|
1765
1778
|
console.warn("[roadmap-sync] GITHUB_TOKEN not found \u2014 external sync skipped");
|
|
1766
1779
|
return;
|
|
1767
1780
|
}
|
|
1768
|
-
const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-
|
|
1781
|
+
const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-6D2F2J57.js");
|
|
1769
1782
|
const adapter = new GitHubIssuesSyncAdapter({
|
|
1770
1783
|
token,
|
|
1771
1784
|
config: trackerConfig
|
|
1772
1785
|
});
|
|
1773
|
-
const result = await fullSync(
|
|
1786
|
+
const result = await fullSync(projectPath, adapter, trackerConfig);
|
|
1774
1787
|
if (result.errors.length > 0) {
|
|
1775
1788
|
for (const err2 of result.errors) {
|
|
1776
1789
|
console.error(
|
|
@@ -1817,7 +1830,8 @@ var manageStateDefinition = {
|
|
|
1817
1830
|
"task-start",
|
|
1818
1831
|
"task-complete",
|
|
1819
1832
|
"phase-start",
|
|
1820
|
-
"phase-complete"
|
|
1833
|
+
"phase-complete",
|
|
1834
|
+
"task-transition"
|
|
1821
1835
|
],
|
|
1822
1836
|
description: "Action to perform"
|
|
1823
1837
|
},
|
|
@@ -1856,18 +1870,42 @@ var manageStateDefinition = {
|
|
|
1856
1870
|
type: "string",
|
|
1857
1871
|
enum: ["active", "resolved", "superseded"],
|
|
1858
1872
|
description: "New status for the entry: active, resolved, or superseded (required for update_entry_status)"
|
|
1859
|
-
}
|
|
1873
|
+
},
|
|
1874
|
+
taskId: { type: "string", description: "Task id (required for task-transition)" },
|
|
1875
|
+
toLane: {
|
|
1876
|
+
type: "string",
|
|
1877
|
+
enum: ["planned", "claimed", "in_progress", "in_review", "done", "blocked", "canceled"],
|
|
1878
|
+
description: "Target lane (required for task-transition)"
|
|
1879
|
+
},
|
|
1880
|
+
dependsOn: {
|
|
1881
|
+
type: "array",
|
|
1882
|
+
items: { type: "string" },
|
|
1883
|
+
description: "Dependency task ids; when set, the task is registered before transitioning"
|
|
1884
|
+
},
|
|
1885
|
+
evidence: {
|
|
1886
|
+
type: "array",
|
|
1887
|
+
items: { type: "string" },
|
|
1888
|
+
description: "PR/commit/test refs (required to enter done)"
|
|
1889
|
+
},
|
|
1890
|
+
force: {
|
|
1891
|
+
type: "boolean",
|
|
1892
|
+
description: "Force an off-table transition (requires actor+reason)"
|
|
1893
|
+
},
|
|
1894
|
+
actor: { type: "string", description: "Actor for a forced transition" },
|
|
1895
|
+
reason: { type: "string", description: "Reason for a forced transition" }
|
|
1860
1896
|
},
|
|
1861
1897
|
required: ["path", "action"]
|
|
1862
1898
|
}
|
|
1863
1899
|
};
|
|
1864
1900
|
async function handleShow(projectPath, input) {
|
|
1865
|
-
const {
|
|
1866
|
-
return resultToMcpResponse(
|
|
1901
|
+
const { readHarnessState } = await import("./state-events-ECQAIZLU.js");
|
|
1902
|
+
return resultToMcpResponse(
|
|
1903
|
+
await readHarnessState(projectPath, { stream: input.stream, session: input.session })
|
|
1904
|
+
);
|
|
1867
1905
|
}
|
|
1868
1906
|
async function handleLearn(projectPath, input) {
|
|
1869
1907
|
if (!input.learning) return mcpError("Error: learning is required for learn action");
|
|
1870
|
-
const { appendLearning } = await import("./dist-
|
|
1908
|
+
const { appendLearning } = await import("./dist-6D2F2J57.js");
|
|
1871
1909
|
const result = await appendLearning(
|
|
1872
1910
|
projectPath,
|
|
1873
1911
|
input.learning,
|
|
@@ -1882,7 +1920,7 @@ async function handleLearn(projectPath, input) {
|
|
|
1882
1920
|
async function handleFailure(projectPath, input) {
|
|
1883
1921
|
if (!input.description) return mcpError("Error: description is required for failure action");
|
|
1884
1922
|
if (!input.failureType) return mcpError("Error: failureType is required for failure action");
|
|
1885
|
-
const { appendFailure } = await import("./dist-
|
|
1923
|
+
const { appendFailure } = await import("./dist-6D2F2J57.js");
|
|
1886
1924
|
const result = await appendFailure(
|
|
1887
1925
|
projectPath,
|
|
1888
1926
|
input.description,
|
|
@@ -1901,19 +1939,22 @@ async function handleFailure(projectPath, input) {
|
|
|
1901
1939
|
return resultToMcpResponse(Ok({ recorded: true }));
|
|
1902
1940
|
}
|
|
1903
1941
|
async function handleArchive(projectPath, input) {
|
|
1904
|
-
const { archiveFailures } = await import("./dist-
|
|
1942
|
+
const { archiveFailures } = await import("./dist-6D2F2J57.js");
|
|
1905
1943
|
const result = await archiveFailures(projectPath, input.stream, input.session);
|
|
1906
1944
|
if (!result.ok) return resultToMcpResponse(result);
|
|
1907
1945
|
return resultToMcpResponse(Ok({ archived: true }));
|
|
1908
1946
|
}
|
|
1909
1947
|
async function handleReset(projectPath, input) {
|
|
1910
|
-
const {
|
|
1911
|
-
const result = await
|
|
1948
|
+
const { eventSourcing } = await import("./dist-6D2F2J57.js");
|
|
1949
|
+
const result = await eventSourcing.resetEventLog(projectPath, {
|
|
1950
|
+
stream: input.stream,
|
|
1951
|
+
session: input.session
|
|
1952
|
+
});
|
|
1912
1953
|
if (!result.ok) return resultToMcpResponse(result);
|
|
1913
1954
|
return resultToMcpResponse(Ok({ reset: true }));
|
|
1914
1955
|
}
|
|
1915
1956
|
async function handleGate(projectPath, input) {
|
|
1916
|
-
const { runMechanicalGate } = await import("./dist-
|
|
1957
|
+
const { runMechanicalGate } = await import("./dist-6D2F2J57.js");
|
|
1917
1958
|
const result = await runMechanicalGate(projectPath);
|
|
1918
1959
|
if (result.ok) {
|
|
1919
1960
|
await emitSkillEvent(projectPath, {
|
|
@@ -1933,7 +1974,7 @@ async function handleGate(projectPath, input) {
|
|
|
1933
1974
|
}
|
|
1934
1975
|
async function handleSaveHandoff(projectPath, input) {
|
|
1935
1976
|
if (!input.handoff) return mcpError("Error: handoff is required for save-handoff action");
|
|
1936
|
-
const { saveHandoff } = await import("./dist-
|
|
1977
|
+
const { saveHandoff } = await import("./dist-6D2F2J57.js");
|
|
1937
1978
|
const result = await saveHandoff(
|
|
1938
1979
|
projectPath,
|
|
1939
1980
|
input.handoff,
|
|
@@ -1955,7 +1996,7 @@ async function handleSaveHandoff(projectPath, input) {
|
|
|
1955
1996
|
return resultToMcpResponse(Ok({ saved: true }));
|
|
1956
1997
|
}
|
|
1957
1998
|
async function handleLoadHandoff(projectPath, input) {
|
|
1958
|
-
const { loadHandoff } = await import("./dist-
|
|
1999
|
+
const { loadHandoff } = await import("./dist-6D2F2J57.js");
|
|
1959
2000
|
return resultToMcpResponse(await loadHandoff(projectPath, input.stream, input.session));
|
|
1960
2001
|
}
|
|
1961
2002
|
async function handleAppendEntry(projectPath, input) {
|
|
@@ -1963,7 +2004,7 @@ async function handleAppendEntry(projectPath, input) {
|
|
|
1963
2004
|
if (!input.authorSkill) return mcpError("Error: authorSkill is required for append_entry action");
|
|
1964
2005
|
if (!input.content) return mcpError("Error: content is required for append_entry action");
|
|
1965
2006
|
if (input.session) {
|
|
1966
|
-
const { appendSessionEntry } = await import("./dist-
|
|
2007
|
+
const { appendSessionEntry } = await import("./dist-6D2F2J57.js");
|
|
1967
2008
|
const result = await appendSessionEntry(
|
|
1968
2009
|
projectPath,
|
|
1969
2010
|
input.session,
|
|
@@ -1976,16 +2017,17 @@ async function handleAppendEntry(projectPath, input) {
|
|
|
1976
2017
|
if (input.section !== "decisions") {
|
|
1977
2018
|
return mcpError("Error: session is required for non-decisions sections");
|
|
1978
2019
|
}
|
|
1979
|
-
const {
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2020
|
+
const { randomUUID: randomUUID10 } = await import("crypto");
|
|
2021
|
+
const { emitCoreEvent } = await import("./state-events-ECQAIZLU.js");
|
|
2022
|
+
const r = await emitCoreEvent(
|
|
2023
|
+
projectPath,
|
|
2024
|
+
{
|
|
2025
|
+
type: "decision_recorded",
|
|
2026
|
+
payload: { id: randomUUID10(), text: input.content, context: input.authorSkill }
|
|
2027
|
+
},
|
|
2028
|
+
{ stream: input.stream }
|
|
2029
|
+
);
|
|
2030
|
+
if (!r.ok) return resultToMcpResponse(r);
|
|
1989
2031
|
return resultToMcpResponse(Ok({ appended: true, target: "global-state" }));
|
|
1990
2032
|
}
|
|
1991
2033
|
async function handleUpdateEntryStatus(projectPath, input) {
|
|
@@ -1994,7 +2036,7 @@ async function handleUpdateEntryStatus(projectPath, input) {
|
|
|
1994
2036
|
if (!input.entryId) return mcpError("Error: entryId is required for update_entry_status action");
|
|
1995
2037
|
if (!input.newStatus)
|
|
1996
2038
|
return mcpError("Error: newStatus is required for update_entry_status action");
|
|
1997
|
-
const { updateSessionEntryStatus } = await import("./dist-
|
|
2039
|
+
const { updateSessionEntryStatus } = await import("./dist-6D2F2J57.js");
|
|
1998
2040
|
const result = await updateSessionEntryStatus(
|
|
1999
2041
|
projectPath,
|
|
2000
2042
|
input.session,
|
|
@@ -2002,12 +2044,21 @@ async function handleUpdateEntryStatus(projectPath, input) {
|
|
|
2002
2044
|
input.entryId,
|
|
2003
2045
|
input.newStatus
|
|
2004
2046
|
);
|
|
2047
|
+
if (result.ok && input.section === "decisions" && input.newStatus === "resolved") {
|
|
2048
|
+
try {
|
|
2049
|
+
const { emitApprovalResolved } = await import("./state-events-ECQAIZLU.js");
|
|
2050
|
+
await emitApprovalResolved(projectPath, input.entryId, input.newStatus, {
|
|
2051
|
+
stream: input.stream
|
|
2052
|
+
});
|
|
2053
|
+
} catch {
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2005
2056
|
return resultToMcpResponse(result);
|
|
2006
2057
|
}
|
|
2007
2058
|
async function handleReadSection(projectPath, input) {
|
|
2008
2059
|
if (!input.session) return mcpError("Error: session is required for read_section action");
|
|
2009
2060
|
if (!input.section) return mcpError("Error: section is required for read_section action");
|
|
2010
|
-
const { readSessionSection } = await import("./dist-
|
|
2061
|
+
const { readSessionSection } = await import("./dist-6D2F2J57.js");
|
|
2011
2062
|
const result = await readSessionSection(
|
|
2012
2063
|
projectPath,
|
|
2013
2064
|
input.session,
|
|
@@ -2017,13 +2068,13 @@ async function handleReadSection(projectPath, input) {
|
|
|
2017
2068
|
}
|
|
2018
2069
|
async function handleReadSections(projectPath, input) {
|
|
2019
2070
|
if (!input.session) return mcpError("Error: session is required for read_sections action");
|
|
2020
|
-
const { readSessionSections } = await import("./dist-
|
|
2071
|
+
const { readSessionSections } = await import("./dist-6D2F2J57.js");
|
|
2021
2072
|
const result = await readSessionSections(projectPath, input.session);
|
|
2022
2073
|
return resultToMcpResponse(result);
|
|
2023
2074
|
}
|
|
2024
2075
|
async function handleArchiveSession(projectPath, input) {
|
|
2025
2076
|
if (!input.session) return mcpError("Error: session is required for archive_session action");
|
|
2026
|
-
const { archiveSession } = await import("./dist-
|
|
2077
|
+
const { archiveSession } = await import("./dist-6D2F2J57.js");
|
|
2027
2078
|
const { buildArchiveHooks } = await import("@harness-engineering/orchestrator");
|
|
2028
2079
|
const hooks = buildArchiveHooks({ projectPath });
|
|
2029
2080
|
const result = await archiveSession(projectPath, input.session, { hooks });
|
|
@@ -2063,6 +2114,30 @@ async function handlePhaseComplete(projectPath, input) {
|
|
|
2063
2114
|
}
|
|
2064
2115
|
return resultToMcpResponse(Ok({ synced: true, trigger: "phase-complete" }));
|
|
2065
2116
|
}
|
|
2117
|
+
async function handleTaskTransition(projectPath, input) {
|
|
2118
|
+
if (!input.taskId) return mcpError("Error: taskId is required for task-transition action");
|
|
2119
|
+
if (!input.toLane) return mcpError("Error: toLane is required for task-transition action");
|
|
2120
|
+
const { eventSourcing } = await import("./dist-6D2F2J57.js");
|
|
2121
|
+
const scope = { stream: input.stream, session: input.session };
|
|
2122
|
+
if (input.dependsOn) {
|
|
2123
|
+
const reg = await eventSourcing.registerTask(projectPath, input.taskId, input.dependsOn, scope);
|
|
2124
|
+
if (!reg.ok) return resultToMcpResponse(reg);
|
|
2125
|
+
}
|
|
2126
|
+
const result = await eventSourcing.transitionLane(
|
|
2127
|
+
projectPath,
|
|
2128
|
+
input.taskId,
|
|
2129
|
+
input.toLane,
|
|
2130
|
+
{
|
|
2131
|
+
...input.evidence !== void 0 ? { evidence: input.evidence } : {},
|
|
2132
|
+
...input.force !== void 0 ? { force: input.force } : {},
|
|
2133
|
+
...input.actor !== void 0 ? { actor: input.actor } : {},
|
|
2134
|
+
...input.reason !== void 0 ? { reason: input.reason } : {}
|
|
2135
|
+
},
|
|
2136
|
+
scope
|
|
2137
|
+
);
|
|
2138
|
+
if (!result.ok) return resultToMcpResponse(result);
|
|
2139
|
+
return resultToMcpResponse(Ok({ taskId: input.taskId, lane: input.toLane }));
|
|
2140
|
+
}
|
|
2066
2141
|
var ACTION_HANDLERS = {
|
|
2067
2142
|
show: handleShow,
|
|
2068
2143
|
learn: handleLearn,
|
|
@@ -2080,7 +2155,8 @@ var ACTION_HANDLERS = {
|
|
|
2080
2155
|
"task-start": handleTaskStart,
|
|
2081
2156
|
"task-complete": handleTaskComplete,
|
|
2082
2157
|
"phase-start": handlePhaseStart,
|
|
2083
|
-
"phase-complete": handlePhaseComplete
|
|
2158
|
+
"phase-complete": handlePhaseComplete,
|
|
2159
|
+
"task-transition": handleTaskTransition
|
|
2084
2160
|
};
|
|
2085
2161
|
async function handleManageState(input) {
|
|
2086
2162
|
try {
|
|
@@ -2105,7 +2181,7 @@ var listStreamsDefinition = {
|
|
|
2105
2181
|
};
|
|
2106
2182
|
async function handleListStreams(input) {
|
|
2107
2183
|
try {
|
|
2108
|
-
const { listStreams, loadStreamIndex } = await import("./dist-
|
|
2184
|
+
const { listStreams, loadStreamIndex } = await import("./dist-6D2F2J57.js");
|
|
2109
2185
|
const projectPath = sanitizePath(input.path);
|
|
2110
2186
|
const indexResult = await loadStreamIndex(projectPath);
|
|
2111
2187
|
const streamsResult = await listStreams(projectPath);
|
|
@@ -2143,7 +2219,7 @@ var checkPhaseGateDefinition = {
|
|
|
2143
2219
|
};
|
|
2144
2220
|
async function handleCheckPhaseGate(input) {
|
|
2145
2221
|
try {
|
|
2146
|
-
const { runCheckPhaseGate } = await import("./check-phase-gate-
|
|
2222
|
+
const { runCheckPhaseGate } = await import("./check-phase-gate-CHU2E3OU.js");
|
|
2147
2223
|
const result = await runCheckPhaseGate({ cwd: sanitizePath(input.path) });
|
|
2148
2224
|
if (result.ok) {
|
|
2149
2225
|
return { content: [{ type: "text", text: JSON.stringify(result.value) }] };
|
|
@@ -2199,7 +2275,7 @@ async function handleValidateCrossCheck(input) {
|
|
|
2199
2275
|
};
|
|
2200
2276
|
}
|
|
2201
2277
|
try {
|
|
2202
|
-
const { runCrossCheck } = await import("./validate-cross-check-
|
|
2278
|
+
const { runCrossCheck } = await import("./validate-cross-check-J5PVGICB.js");
|
|
2203
2279
|
const specsDir = path12.resolve(projectPath, input.specsDir ?? "docs/specs");
|
|
2204
2280
|
if (!specsDir.startsWith(projectPath)) {
|
|
2205
2281
|
return {
|
|
@@ -2708,11 +2784,11 @@ function resolveOutputDir(platform, opts) {
|
|
|
2708
2784
|
}
|
|
2709
2785
|
async function confirmDeletion(files) {
|
|
2710
2786
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2711
|
-
return new Promise((
|
|
2787
|
+
return new Promise((resolve11) => {
|
|
2712
2788
|
rl.question(`
|
|
2713
2789
|
Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
|
|
2714
2790
|
rl.close();
|
|
2715
|
-
|
|
2791
|
+
resolve11(answer.toLowerCase() === "y");
|
|
2716
2792
|
});
|
|
2717
2793
|
});
|
|
2718
2794
|
}
|
|
@@ -3022,9 +3098,10 @@ async function handleGenerateSlashCommands(input) {
|
|
|
3022
3098
|
// src/mcp/resources/state.ts
|
|
3023
3099
|
async function getStateResource(projectRoot) {
|
|
3024
3100
|
try {
|
|
3025
|
-
const {
|
|
3101
|
+
const { migrateToStreams } = await import("./dist-6D2F2J57.js");
|
|
3102
|
+
const { readHarnessState } = await import("./state-events-ECQAIZLU.js");
|
|
3026
3103
|
await migrateToStreams(projectRoot);
|
|
3027
|
-
const result = await
|
|
3104
|
+
const result = await readHarnessState(projectRoot);
|
|
3028
3105
|
if (result.ok) {
|
|
3029
3106
|
return JSON.stringify(result.value, null, 2);
|
|
3030
3107
|
}
|
|
@@ -3140,7 +3217,7 @@ async function handleQueryGraph(input) {
|
|
|
3140
3217
|
const projectPath = sanitizePath(input.path);
|
|
3141
3218
|
const store = await loadGraphStore(projectPath);
|
|
3142
3219
|
if (!store) return graphNotFoundError();
|
|
3143
|
-
const { ContextQL } = await import("./dist-
|
|
3220
|
+
const { ContextQL } = await import("./dist-DWBTZXR5.js");
|
|
3144
3221
|
const cql = new ContextQL(store);
|
|
3145
3222
|
const result = cql.execute({
|
|
3146
3223
|
rootNodeIds: input.rootNodeIds,
|
|
@@ -3222,7 +3299,7 @@ async function handleSearchSimilar(input) {
|
|
|
3222
3299
|
const projectPath = sanitizePath(input.path);
|
|
3223
3300
|
const store = await loadGraphStore(projectPath);
|
|
3224
3301
|
if (!store) return graphNotFoundError();
|
|
3225
|
-
const { FusionLayer } = await import("./dist-
|
|
3302
|
+
const { FusionLayer } = await import("./dist-DWBTZXR5.js");
|
|
3226
3303
|
const fusion = new FusionLayer(store);
|
|
3227
3304
|
const results = fusion.search(input.query, input.topK ?? 10);
|
|
3228
3305
|
if (input.mode === "summary") {
|
|
@@ -3277,7 +3354,7 @@ async function handleFindContextFor(input) {
|
|
|
3277
3354
|
const projectPath = sanitizePath(input.path);
|
|
3278
3355
|
const store = await loadGraphStore(projectPath);
|
|
3279
3356
|
if (!store) return graphNotFoundError();
|
|
3280
|
-
const { FusionLayer, ContextQL } = await import("./dist-
|
|
3357
|
+
const { FusionLayer, ContextQL } = await import("./dist-DWBTZXR5.js");
|
|
3281
3358
|
const fusion = new FusionLayer(store);
|
|
3282
3359
|
const cql = new ContextQL(store);
|
|
3283
3360
|
const tokenBudget = input.tokenBudget ?? 4e3;
|
|
@@ -3382,7 +3459,7 @@ async function handleGetRelationships(input) {
|
|
|
3382
3459
|
const projectPath = sanitizePath(input.path);
|
|
3383
3460
|
const store = await loadGraphStore(projectPath);
|
|
3384
3461
|
if (!store) return graphNotFoundError();
|
|
3385
|
-
const { ContextQL } = await import("./dist-
|
|
3462
|
+
const { ContextQL } = await import("./dist-DWBTZXR5.js");
|
|
3386
3463
|
const cql = new ContextQL(store);
|
|
3387
3464
|
const direction = input.direction ?? "both";
|
|
3388
3465
|
const bidirectional = direction === "both" || direction === "inbound";
|
|
@@ -3500,7 +3577,7 @@ async function handleGetImpact(input) {
|
|
|
3500
3577
|
const projectPath = sanitizePath(input.path);
|
|
3501
3578
|
const store = await loadGraphStore(projectPath);
|
|
3502
3579
|
if (!store) return graphNotFoundError();
|
|
3503
|
-
const { ContextQL } = await import("./dist-
|
|
3580
|
+
const { ContextQL } = await import("./dist-DWBTZXR5.js");
|
|
3504
3581
|
let targetNodeId = input.nodeId;
|
|
3505
3582
|
if (!targetNodeId && input.filePath) {
|
|
3506
3583
|
const fileNodes = store.findNodes({ type: "file" });
|
|
@@ -3632,9 +3709,9 @@ async function handleIngestSource(input) {
|
|
|
3632
3709
|
try {
|
|
3633
3710
|
const projectPath = sanitizePath(input.path);
|
|
3634
3711
|
const graphDir = path16.join(projectPath, ".harness", "graph");
|
|
3635
|
-
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-
|
|
3636
|
-
const
|
|
3637
|
-
await
|
|
3712
|
+
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-DWBTZXR5.js");
|
|
3713
|
+
const fs53 = await import("fs/promises");
|
|
3714
|
+
await fs53.mkdir(graphDir, { recursive: true });
|
|
3638
3715
|
const store = new GraphStore();
|
|
3639
3716
|
await store.load(graphDir);
|
|
3640
3717
|
const results = [];
|
|
@@ -3656,13 +3733,13 @@ async function handleIngestSource(input) {
|
|
|
3656
3733
|
results.push(gitResult);
|
|
3657
3734
|
}
|
|
3658
3735
|
if (input.source === "business-signals" || input.source === "all") {
|
|
3659
|
-
const { createExtractionRunner } = await import("./dist-
|
|
3736
|
+
const { createExtractionRunner } = await import("./dist-DWBTZXR5.js");
|
|
3660
3737
|
const extractedDir = path16.join(projectPath, ".harness", "knowledge", "extracted");
|
|
3661
3738
|
const signalsResult = await createExtractionRunner().run(projectPath, store, extractedDir);
|
|
3662
3739
|
results.push(signalsResult);
|
|
3663
3740
|
}
|
|
3664
3741
|
if (input.source === "diagrams" || input.source === "all") {
|
|
3665
|
-
const { DiagramParser } = await import("./dist-
|
|
3742
|
+
const { DiagramParser } = await import("./dist-DWBTZXR5.js");
|
|
3666
3743
|
const diagramParser = new DiagramParser(store);
|
|
3667
3744
|
const diagramResult = await diagramParser.ingest(projectPath);
|
|
3668
3745
|
results.push(diagramResult);
|
|
@@ -3728,7 +3805,7 @@ async function handleDetectAnomalies(input) {
|
|
|
3728
3805
|
const projectPath = sanitizePath(input.path);
|
|
3729
3806
|
const store = await loadGraphStore(projectPath);
|
|
3730
3807
|
if (!store) return graphNotFoundError();
|
|
3731
|
-
const { GraphAnomalyAdapter } = await import("./dist-
|
|
3808
|
+
const { GraphAnomalyAdapter } = await import("./dist-DWBTZXR5.js");
|
|
3732
3809
|
const adapter = new GraphAnomalyAdapter(store);
|
|
3733
3810
|
const report = adapter.detect({
|
|
3734
3811
|
...input.threshold !== void 0 && { threshold: input.threshold },
|
|
@@ -3777,7 +3854,7 @@ async function handleAskGraph(input) {
|
|
|
3777
3854
|
const projectPath = sanitizePath(input.path);
|
|
3778
3855
|
const store = await loadGraphStore(projectPath);
|
|
3779
3856
|
if (!store) return graphNotFoundError();
|
|
3780
|
-
const { askGraph } = await import("./dist-
|
|
3857
|
+
const { askGraph } = await import("./dist-DWBTZXR5.js");
|
|
3781
3858
|
const result = await askGraph(store, input.question);
|
|
3782
3859
|
return {
|
|
3783
3860
|
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
@@ -3841,7 +3918,7 @@ async function handleComputeBlastRadius(input) {
|
|
|
3841
3918
|
const projectPath = sanitizePath(input.path);
|
|
3842
3919
|
const store = await loadGraphStore(projectPath);
|
|
3843
3920
|
if (!store) return graphNotFoundError();
|
|
3844
|
-
const { CascadeSimulator } = await import("./dist-
|
|
3921
|
+
const { CascadeSimulator } = await import("./dist-DWBTZXR5.js");
|
|
3845
3922
|
let targetNodeId = input.nodeId;
|
|
3846
3923
|
if (!targetNodeId && input.file) {
|
|
3847
3924
|
const fileNodes = store.findNodes({ type: "file" });
|
|
@@ -4042,7 +4119,7 @@ var generateAgentDefinitionsDefinition = {
|
|
|
4042
4119
|
}
|
|
4043
4120
|
};
|
|
4044
4121
|
async function handleGenerateAgentDefinitions(input) {
|
|
4045
|
-
const { generateAgentDefinitions } = await import("./generate-agent-definitions-
|
|
4122
|
+
const { generateAgentDefinitions } = await import("./generate-agent-definitions-KPVGBGDY.js");
|
|
4046
4123
|
const platforms = input.platform === "all" || !input.platform ? ["claude-code", "gemini-cli"] : [input.platform];
|
|
4047
4124
|
const results = generateAgentDefinitions({
|
|
4048
4125
|
platforms: [...platforms],
|
|
@@ -4078,7 +4155,7 @@ async function handleManageRoadmapFileLess(input, client) {
|
|
|
4078
4155
|
case "sync":
|
|
4079
4156
|
return handleSync();
|
|
4080
4157
|
case "groom":
|
|
4081
|
-
return err("Error: groom is only supported in file-based roadmap mode
|
|
4158
|
+
return err("Error: groom is only supported in file-based roadmap mode.");
|
|
4082
4159
|
default:
|
|
4083
4160
|
return err(`Error: unknown action`);
|
|
4084
4161
|
}
|
|
@@ -4278,7 +4355,7 @@ function renderRoadmap(features) {
|
|
|
4278
4355
|
// src/mcp/tools/roadmap.ts
|
|
4279
4356
|
var manageRoadmapDefinition = {
|
|
4280
4357
|
name: "manage_roadmap",
|
|
4281
|
-
description: 'Manage the project roadmap: show, add, update, remove, promote, sync, groom features, or query by filter. Reads and writes
|
|
4358
|
+
description: 'Manage the project roadmap: show, add, update, remove, promote, sync, groom features, or query by filter. Reads and writes the project roadmap (sharded or single-file). The "promote" action transitions an existing row toward planned (backlog\u2192planned) and links its spec atomically \u2014 creating a new planned row under the "Intake" lane if the feature does not exist \u2014 returning a structured RoadmapPromoteResult envelope. The "groom" action tidies the roadmap: it demotes unactionable planned rows (no spec & no plan) to backlog and moves completed features into docs/roadmap-archive.md, returning the list of changes.',
|
|
4282
4359
|
inputSchema: {
|
|
4283
4360
|
type: "object",
|
|
4284
4361
|
properties: {
|
|
@@ -4339,22 +4416,12 @@ var manageRoadmapDefinition = {
|
|
|
4339
4416
|
required: ["path", "action"]
|
|
4340
4417
|
}
|
|
4341
4418
|
};
|
|
4342
|
-
function
|
|
4343
|
-
return
|
|
4344
|
-
}
|
|
4345
|
-
function readRoadmapFile(projectRoot) {
|
|
4346
|
-
const filePath = roadmapPath(projectRoot);
|
|
4347
|
-
try {
|
|
4348
|
-
return fs15.readFileSync(filePath, "utf-8");
|
|
4349
|
-
} catch {
|
|
4350
|
-
return null;
|
|
4351
|
-
}
|
|
4419
|
+
async function loadRoadmap(projectRoot) {
|
|
4420
|
+
return resolveRoadmapStore({ projectRoot }).load();
|
|
4352
4421
|
}
|
|
4353
|
-
function
|
|
4354
|
-
const
|
|
4355
|
-
|
|
4356
|
-
fs15.mkdirSync(dir, { recursive: true });
|
|
4357
|
-
fs15.writeFileSync(filePath, content, "utf-8");
|
|
4422
|
+
async function persistRoadmap(projectRoot, before, after) {
|
|
4423
|
+
const store = resolveRoadmapStore({ projectRoot });
|
|
4424
|
+
return applyRoadmapDiff(store, before, after);
|
|
4358
4425
|
}
|
|
4359
4426
|
function archiveFilePath(projectRoot) {
|
|
4360
4427
|
return path18.join(projectRoot, "docs", "roadmap-archive.md");
|
|
@@ -4393,17 +4460,16 @@ function roadmapNotFoundError() {
|
|
|
4393
4460
|
content: [
|
|
4394
4461
|
{
|
|
4395
4462
|
type: "text",
|
|
4396
|
-
text: "Error:
|
|
4463
|
+
text: "Error: roadmap not found. Create a roadmap first."
|
|
4397
4464
|
}
|
|
4398
4465
|
],
|
|
4399
4466
|
isError: true
|
|
4400
4467
|
};
|
|
4401
4468
|
}
|
|
4402
|
-
function handleShow3(projectPath, input, deps) {
|
|
4403
|
-
const {
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
const result = parseRoadmap(raw);
|
|
4469
|
+
async function handleShow3(projectPath, input, deps) {
|
|
4470
|
+
const { Ok: Ok2 } = deps;
|
|
4471
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4472
|
+
const result = await loadRoadmap(projectPath);
|
|
4407
4473
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4408
4474
|
let roadmap = result.value;
|
|
4409
4475
|
if (input.milestone) {
|
|
@@ -4459,15 +4525,15 @@ function buildFeatureFromInput(input) {
|
|
|
4459
4525
|
updatedAt: null
|
|
4460
4526
|
};
|
|
4461
4527
|
}
|
|
4462
|
-
function handleAdd2(projectPath, input, deps) {
|
|
4463
|
-
const {
|
|
4528
|
+
async function handleAdd2(projectPath, input, deps) {
|
|
4529
|
+
const { Ok: Ok2 } = deps;
|
|
4464
4530
|
const validationError = validateAddFields(input);
|
|
4465
4531
|
if (validationError) return validationError;
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
const result = parseRoadmap(raw);
|
|
4532
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4533
|
+
const result = await loadRoadmap(projectPath);
|
|
4469
4534
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4470
4535
|
const roadmap = result.value;
|
|
4536
|
+
const before = structuredClone(roadmap);
|
|
4471
4537
|
const milestone = roadmap.milestones.find(
|
|
4472
4538
|
(m) => m.name.toLowerCase() === input.milestone.toLowerCase()
|
|
4473
4539
|
);
|
|
@@ -4479,7 +4545,8 @@ function handleAdd2(projectPath, input, deps) {
|
|
|
4479
4545
|
}
|
|
4480
4546
|
milestone.features.push(buildFeatureFromInput(input));
|
|
4481
4547
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4482
|
-
|
|
4548
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4549
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4483
4550
|
return resultToMcpResponse(Ok2(roadmap));
|
|
4484
4551
|
}
|
|
4485
4552
|
function claimRefusedResponse(featureName, roadmap, currentAssignee) {
|
|
@@ -4494,19 +4561,19 @@ function claimRefusedResponse(featureName, roadmap, currentAssignee) {
|
|
|
4494
4561
|
isError: true
|
|
4495
4562
|
};
|
|
4496
4563
|
}
|
|
4497
|
-
function handleUpdate2(projectPath, input, deps) {
|
|
4498
|
-
const {
|
|
4564
|
+
async function handleUpdate2(projectPath, input, deps) {
|
|
4565
|
+
const { syncRoadmap, applySyncChanges, Ok: Ok2 } = deps;
|
|
4499
4566
|
if (!input.feature) {
|
|
4500
4567
|
return {
|
|
4501
4568
|
content: [{ type: "text", text: "Error: feature is required for update action" }],
|
|
4502
4569
|
isError: true
|
|
4503
4570
|
};
|
|
4504
4571
|
}
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
const result = parseRoadmap(raw);
|
|
4572
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4573
|
+
const result = await loadRoadmap(projectPath);
|
|
4508
4574
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4509
4575
|
const roadmap = result.value;
|
|
4576
|
+
const before = structuredClone(roadmap);
|
|
4510
4577
|
let found = false;
|
|
4511
4578
|
for (const m of roadmap.milestones) {
|
|
4512
4579
|
const feature = m.features.find((f) => f.name.toLowerCase() === input.feature.toLowerCase());
|
|
@@ -4537,27 +4604,31 @@ function handleUpdate2(projectPath, input, deps) {
|
|
|
4537
4604
|
isError: true
|
|
4538
4605
|
};
|
|
4539
4606
|
}
|
|
4540
|
-
const cascade = syncRoadmap({ projectPath, roadmap });
|
|
4541
|
-
if (cascade.ok
|
|
4542
|
-
|
|
4607
|
+
const cascade = await syncRoadmap({ projectPath, roadmap });
|
|
4608
|
+
if (cascade.ok) {
|
|
4609
|
+
const unblocking = cascade.value.filter((change) => change.to !== "blocked");
|
|
4610
|
+
if (unblocking.length > 0) {
|
|
4611
|
+
applySyncChanges(roadmap, unblocking);
|
|
4612
|
+
}
|
|
4543
4613
|
}
|
|
4544
4614
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4545
|
-
|
|
4615
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4616
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4546
4617
|
return resultToMcpResponse(Ok2(roadmap));
|
|
4547
4618
|
}
|
|
4548
|
-
function handleRemove2(projectPath, input, deps) {
|
|
4549
|
-
const {
|
|
4619
|
+
async function handleRemove2(projectPath, input, deps) {
|
|
4620
|
+
const { Ok: Ok2 } = deps;
|
|
4550
4621
|
if (!input.feature) {
|
|
4551
4622
|
return {
|
|
4552
4623
|
content: [{ type: "text", text: "Error: feature is required for remove action" }],
|
|
4553
4624
|
isError: true
|
|
4554
4625
|
};
|
|
4555
4626
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
const result = parseRoadmap(raw);
|
|
4627
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4628
|
+
const result = await loadRoadmap(projectPath);
|
|
4559
4629
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4560
4630
|
const roadmap = result.value;
|
|
4631
|
+
const before = structuredClone(roadmap);
|
|
4561
4632
|
let found = false;
|
|
4562
4633
|
for (const m of roadmap.milestones) {
|
|
4563
4634
|
const idx = m.features.findIndex((f) => f.name.toLowerCase() === input.feature.toLowerCase());
|
|
@@ -4574,7 +4645,8 @@ function handleRemove2(projectPath, input, deps) {
|
|
|
4574
4645
|
};
|
|
4575
4646
|
}
|
|
4576
4647
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4577
|
-
|
|
4648
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4649
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4578
4650
|
return resultToMcpResponse(Ok2(roadmap));
|
|
4579
4651
|
}
|
|
4580
4652
|
function promoteEnvelopeResponse(envelope2) {
|
|
@@ -4583,8 +4655,8 @@ function promoteEnvelopeResponse(envelope2) {
|
|
|
4583
4655
|
isError: envelope2.ok === false
|
|
4584
4656
|
};
|
|
4585
4657
|
}
|
|
4586
|
-
function handlePromote2(projectPath, input, deps) {
|
|
4587
|
-
const {
|
|
4658
|
+
async function handlePromote2(projectPath, input, deps) {
|
|
4659
|
+
const { promoteFeature } = deps;
|
|
4588
4660
|
if (!input.feature) {
|
|
4589
4661
|
return {
|
|
4590
4662
|
content: [{ type: "text", text: "Error: feature is required for promote action" }],
|
|
@@ -4597,9 +4669,8 @@ function handlePromote2(projectPath, input, deps) {
|
|
|
4597
4669
|
isError: true
|
|
4598
4670
|
};
|
|
4599
4671
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
const result = parseRoadmap(raw);
|
|
4672
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4673
|
+
const result = await loadRoadmap(projectPath);
|
|
4603
4674
|
if (!result.ok) {
|
|
4604
4675
|
return promoteEnvelopeResponse({
|
|
4605
4676
|
ok: false,
|
|
@@ -4608,6 +4679,7 @@ function handlePromote2(projectPath, input, deps) {
|
|
|
4608
4679
|
detail: result.error.message
|
|
4609
4680
|
});
|
|
4610
4681
|
}
|
|
4682
|
+
const before = structuredClone(result.value);
|
|
4611
4683
|
const { result: envelope2, nextRoadmap } = promoteFeature(result.value, {
|
|
4612
4684
|
feature: input.feature,
|
|
4613
4685
|
spec: input.spec,
|
|
@@ -4615,30 +4687,28 @@ function handlePromote2(projectPath, input, deps) {
|
|
|
4615
4687
|
});
|
|
4616
4688
|
if (envelope2.ok && envelope2.transitioned !== "noop") {
|
|
4617
4689
|
nextRoadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
} catch (error) {
|
|
4690
|
+
const persisted = await persistRoadmap(projectPath, before, nextRoadmap);
|
|
4691
|
+
if (!persisted.ok) {
|
|
4621
4692
|
return promoteEnvelopeResponse({
|
|
4622
4693
|
ok: false,
|
|
4623
4694
|
reason: "write-failed",
|
|
4624
4695
|
feature: input.feature,
|
|
4625
|
-
detail: error
|
|
4696
|
+
detail: persisted.error.message
|
|
4626
4697
|
});
|
|
4627
4698
|
}
|
|
4628
4699
|
}
|
|
4629
4700
|
return promoteEnvelopeResponse(envelope2);
|
|
4630
4701
|
}
|
|
4631
|
-
function handleQuery2(projectPath, input, deps) {
|
|
4632
|
-
const {
|
|
4702
|
+
async function handleQuery2(projectPath, input, deps) {
|
|
4703
|
+
const { Ok: Ok2 } = deps;
|
|
4633
4704
|
if (!input.filter) {
|
|
4634
4705
|
return {
|
|
4635
4706
|
content: [{ type: "text", text: "Error: filter is required for query action" }],
|
|
4636
4707
|
isError: true
|
|
4637
4708
|
};
|
|
4638
4709
|
}
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
const result = parseRoadmap(raw);
|
|
4710
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4711
|
+
const result = await loadRoadmap(projectPath);
|
|
4642
4712
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4643
4713
|
const roadmap = result.value;
|
|
4644
4714
|
const allFeatures = roadmap.milestones.flatMap(
|
|
@@ -4654,14 +4724,14 @@ function handleQuery2(projectPath, input, deps) {
|
|
|
4654
4724
|
}
|
|
4655
4725
|
return resultToMcpResponse(Ok2(filtered));
|
|
4656
4726
|
}
|
|
4657
|
-
function handleSync2(projectPath, input, deps) {
|
|
4658
|
-
const {
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
const result = parseRoadmap(raw);
|
|
4727
|
+
async function handleSync2(projectPath, input, deps) {
|
|
4728
|
+
const { syncRoadmap, Ok: Ok2 } = deps;
|
|
4729
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4730
|
+
const result = await loadRoadmap(projectPath);
|
|
4662
4731
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4663
4732
|
const roadmap = result.value;
|
|
4664
|
-
const
|
|
4733
|
+
const before = structuredClone(roadmap);
|
|
4734
|
+
const syncResult = await syncRoadmap({
|
|
4665
4735
|
projectPath,
|
|
4666
4736
|
roadmap,
|
|
4667
4737
|
forceSync: input.force_sync ?? false
|
|
@@ -4673,17 +4743,18 @@ function handleSync2(projectPath, input, deps) {
|
|
|
4673
4743
|
}
|
|
4674
4744
|
if (input.apply) {
|
|
4675
4745
|
deps.applySyncChanges(roadmap, changes);
|
|
4676
|
-
|
|
4746
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4747
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4677
4748
|
return resultToMcpResponse(Ok2({ changes, applied: true, roadmap }));
|
|
4678
4749
|
}
|
|
4679
4750
|
return resultToMcpResponse(Ok2({ changes, applied: false }));
|
|
4680
4751
|
}
|
|
4681
|
-
function handleGroom(projectPath, _input, deps) {
|
|
4682
|
-
const {
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
const result = parseRoadmap(raw);
|
|
4752
|
+
async function handleGroom(projectPath, _input, deps) {
|
|
4753
|
+
const { groomRoadmap, Ok: Ok2 } = deps;
|
|
4754
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4755
|
+
const result = await loadRoadmap(projectPath);
|
|
4686
4756
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4757
|
+
const before = structuredClone(result.value);
|
|
4687
4758
|
const { roadmap: groomed, archived, changes } = groomRoadmap(result.value);
|
|
4688
4759
|
if (changes.length === 0) {
|
|
4689
4760
|
return resultToMcpResponse(
|
|
@@ -4692,7 +4763,8 @@ function handleGroom(projectPath, _input, deps) {
|
|
|
4692
4763
|
}
|
|
4693
4764
|
appendToArchive(projectPath, archived, groomed.frontmatter.project, deps);
|
|
4694
4765
|
groomed.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4695
|
-
|
|
4766
|
+
const persisted = await persistRoadmap(projectPath, before, groomed);
|
|
4767
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4696
4768
|
const demoted = changes.filter((c) => c.kind === "demoted").length;
|
|
4697
4769
|
return resultToMcpResponse(
|
|
4698
4770
|
Ok2({
|
|
@@ -4704,7 +4776,7 @@ function handleGroom(projectPath, _input, deps) {
|
|
|
4704
4776
|
);
|
|
4705
4777
|
}
|
|
4706
4778
|
var readOnlyActions = /* @__PURE__ */ new Set(["show", "query"]);
|
|
4707
|
-
function dispatchAction(action, projectPath, input, deps) {
|
|
4779
|
+
async function dispatchAction(action, projectPath, input, deps) {
|
|
4708
4780
|
switch (action) {
|
|
4709
4781
|
case "show":
|
|
4710
4782
|
return handleShow3(projectPath, input, deps);
|
|
@@ -4764,7 +4836,7 @@ async function handleManageRoadmap(input) {
|
|
|
4764
4836
|
release,
|
|
4765
4837
|
promoteFeature,
|
|
4766
4838
|
groomRoadmap
|
|
4767
|
-
} = await import("./dist-
|
|
4839
|
+
} = await import("./dist-6D2F2J57.js");
|
|
4768
4840
|
const { Ok: Ok2 } = await import("./dist-ICW4QUEW.js");
|
|
4769
4841
|
const projectPath = projectPathPre;
|
|
4770
4842
|
const deps = {
|
|
@@ -4780,9 +4852,9 @@ async function handleManageRoadmap(input) {
|
|
|
4780
4852
|
groomRoadmap,
|
|
4781
4853
|
Ok: Ok2
|
|
4782
4854
|
};
|
|
4783
|
-
const response = dispatchAction(input.action, projectPath, input, deps);
|
|
4855
|
+
const response = await dispatchAction(input.action, projectPath, input, deps);
|
|
4784
4856
|
if (shouldTriggerExternalSync(input, response)) {
|
|
4785
|
-
await triggerExternalSync(projectPath
|
|
4857
|
+
await triggerExternalSync(projectPath).catch(() => {
|
|
4786
4858
|
});
|
|
4787
4859
|
}
|
|
4788
4860
|
return response;
|
|
@@ -5208,7 +5280,7 @@ async function handleTransition(validInput, projectPath, id) {
|
|
|
5208
5280
|
const transition = transitionResult.data;
|
|
5209
5281
|
const prompt = renderTransition(transition);
|
|
5210
5282
|
try {
|
|
5211
|
-
const { saveHandoff } = await import("./dist-
|
|
5283
|
+
const { saveHandoff } = await import("./dist-6D2F2J57.js");
|
|
5212
5284
|
await saveHandoff(
|
|
5213
5285
|
projectPath,
|
|
5214
5286
|
{
|
|
@@ -5289,17 +5361,17 @@ async function handleEmitInteraction(input) {
|
|
|
5289
5361
|
}
|
|
5290
5362
|
async function recordInteraction(projectPath, id, type, decision, stream) {
|
|
5291
5363
|
try {
|
|
5292
|
-
const {
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
}
|
|
5364
|
+
const { emitCoreEvent, emitUserInputCaptured, emitApprovalRequested } = await import("./state-events-ECQAIZLU.js");
|
|
5365
|
+
await emitCoreEvent(
|
|
5366
|
+
projectPath,
|
|
5367
|
+
{
|
|
5368
|
+
type: "decision_recorded",
|
|
5369
|
+
payload: { id, text: `[${type}:${id}] ${decision}`, context: "pending user response" }
|
|
5370
|
+
},
|
|
5371
|
+
{ stream }
|
|
5372
|
+
);
|
|
5373
|
+
await emitUserInputCaptured(projectPath, decision, id, { stream });
|
|
5374
|
+
await emitApprovalRequested(projectPath, id, type, decision, { stream });
|
|
5303
5375
|
} catch {
|
|
5304
5376
|
}
|
|
5305
5377
|
}
|
|
@@ -5450,10 +5522,10 @@ async function handleGatherContext(input) {
|
|
|
5450
5522
|
input.include ?? ["state", "learnings", "handoff", "graph", "validation", "businessKnowledge"]
|
|
5451
5523
|
);
|
|
5452
5524
|
const errors = [];
|
|
5453
|
-
const statePromise = includeSet.has("state") ? import("./
|
|
5454
|
-
(
|
|
5525
|
+
const statePromise = includeSet.has("state") ? import("./state-events-ECQAIZLU.js").then(
|
|
5526
|
+
(m) => m.readHarnessState(projectPath, { session: input.session })
|
|
5455
5527
|
) : Promise.resolve(null);
|
|
5456
|
-
const learningsPromise = includeSet.has("learnings") ? import("./dist-
|
|
5528
|
+
const learningsPromise = includeSet.has("learnings") ? import("./dist-6D2F2J57.js").then(
|
|
5457
5529
|
(core) => core.loadBudgetedLearnings(projectPath, {
|
|
5458
5530
|
intent: input.intent,
|
|
5459
5531
|
tokenBudget: input.learningsBudget ?? 1e3,
|
|
@@ -5462,14 +5534,14 @@ async function handleGatherContext(input) {
|
|
|
5462
5534
|
...input.depth !== void 0 && { depth: input.depth }
|
|
5463
5535
|
})
|
|
5464
5536
|
) : Promise.resolve(null);
|
|
5465
|
-
const handoffPromise = includeSet.has("handoff") ? import("./dist-
|
|
5537
|
+
const handoffPromise = includeSet.has("handoff") ? import("./dist-6D2F2J57.js").then(
|
|
5466
5538
|
(core) => core.loadHandoff(projectPath, void 0, input.session)
|
|
5467
5539
|
) : Promise.resolve(null);
|
|
5468
5540
|
const graphPromise = includeSet.has("graph") ? (async () => {
|
|
5469
|
-
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-
|
|
5541
|
+
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-STFGYKZH.js");
|
|
5470
5542
|
const store = await loadGraphStore2(projectPath);
|
|
5471
5543
|
if (!store) return null;
|
|
5472
|
-
const { FusionLayer, ContextQL } = await import("./dist-
|
|
5544
|
+
const { FusionLayer, ContextQL } = await import("./dist-DWBTZXR5.js");
|
|
5473
5545
|
const fusion = new FusionLayer(store);
|
|
5474
5546
|
const cql = new ContextQL(store);
|
|
5475
5547
|
const tokenBudget = input.tokenBudget ?? 4e3;
|
|
@@ -5506,19 +5578,15 @@ async function handleGatherContext(input) {
|
|
|
5506
5578
|
context: contextBlocks
|
|
5507
5579
|
};
|
|
5508
5580
|
})() : Promise.resolve(null);
|
|
5509
|
-
const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-
|
|
5581
|
+
const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-6D2F2J57.js").then(
|
|
5510
5582
|
(core) => core.readSessionSections(projectPath, input.session)
|
|
5511
5583
|
) : Promise.resolve(null);
|
|
5512
5584
|
const shouldIncludeEvents = input.includeEvents !== void 0 ? input.includeEvents : includeSet.has("events") || !!input.session && !input.include;
|
|
5513
|
-
const eventsPromise = shouldIncludeEvents ? import("./
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
});
|
|
5517
|
-
if (!result.ok) return null;
|
|
5518
|
-
return core.formatEventTimeline(result.value);
|
|
5519
|
-
}) : Promise.resolve(null);
|
|
5585
|
+
const eventsPromise = shouldIncludeEvents ? import("./state-events-ECQAIZLU.js").then(
|
|
5586
|
+
(m) => m.readAuditTimeline(projectPath, { session: input.session })
|
|
5587
|
+
) : Promise.resolve(null);
|
|
5520
5588
|
const validationPromise = includeSet.has("validation") ? (async () => {
|
|
5521
|
-
const { handleValidateProject: handleValidateProject2 } = await import("./validate-
|
|
5589
|
+
const { handleValidateProject: handleValidateProject2 } = await import("./validate-64KTEGIE.js");
|
|
5522
5590
|
const result = await handleValidateProject2({ path: projectPath });
|
|
5523
5591
|
const first = result.content[0];
|
|
5524
5592
|
return first ? JSON.parse(first.text) : null;
|
|
@@ -5621,7 +5689,7 @@ async function handleGatherContext(input) {
|
|
|
5621
5689
|
};
|
|
5622
5690
|
if (input.session) {
|
|
5623
5691
|
try {
|
|
5624
|
-
const core = await import("./dist-
|
|
5692
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
5625
5693
|
core.updateSessionIndex(
|
|
5626
5694
|
projectPath,
|
|
5627
5695
|
input.session,
|
|
@@ -5819,7 +5887,7 @@ async function handleReviewChanges(input) {
|
|
|
5819
5887
|
}
|
|
5820
5888
|
}
|
|
5821
5889
|
async function runQuickReview(projectPath, diff, diffLines, downgraded, offset, limit) {
|
|
5822
|
-
const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-
|
|
5890
|
+
const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-BPHADTRQ.js");
|
|
5823
5891
|
const result = await handleAnalyzeDiff2({ diff, path: projectPath });
|
|
5824
5892
|
const firstContent = result.content[0];
|
|
5825
5893
|
if (!firstContent) throw new Error("Empty analyze_diff response");
|
|
@@ -5854,7 +5922,7 @@ function extractFileCount(diffParsed) {
|
|
|
5854
5922
|
return files?.length ?? 0;
|
|
5855
5923
|
}
|
|
5856
5924
|
async function runStandardReview(projectPath, diff, diffLines, downgraded, offset, limit) {
|
|
5857
|
-
const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-
|
|
5925
|
+
const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-BPHADTRQ.js");
|
|
5858
5926
|
const [diffResult, reviewResult] = await Promise.all([
|
|
5859
5927
|
handleAnalyzeDiff2({ diff, path: projectPath }),
|
|
5860
5928
|
handleCreateSelfReview2({ path: projectPath, diff })
|
|
@@ -5889,7 +5957,7 @@ async function runStandardReview(projectPath, diff, diffLines, downgraded, offse
|
|
|
5889
5957
|
};
|
|
5890
5958
|
}
|
|
5891
5959
|
async function runDeepReview(projectPath, diff, diffLines, _downgraded, offset, limit) {
|
|
5892
|
-
const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-
|
|
5960
|
+
const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-2TFQ5OAE.js");
|
|
5893
5961
|
const result = await handleRunCodeReview2({
|
|
5894
5962
|
path: projectPath,
|
|
5895
5963
|
diff,
|
|
@@ -5969,7 +6037,7 @@ async function handleCheckTaskIndependence(input) {
|
|
|
5969
6037
|
try {
|
|
5970
6038
|
const projectPath = sanitizePath(input.path);
|
|
5971
6039
|
const store = await loadGraphStore(projectPath);
|
|
5972
|
-
const { TaskIndependenceAnalyzer } = await import("./dist-
|
|
6040
|
+
const { TaskIndependenceAnalyzer } = await import("./dist-DWBTZXR5.js");
|
|
5973
6041
|
const analyzer = new TaskIndependenceAnalyzer(store ?? void 0);
|
|
5974
6042
|
const result = analyzer.analyze({
|
|
5975
6043
|
tasks: input.tasks,
|
|
@@ -6057,7 +6125,7 @@ async function handlePredictConflicts(input) {
|
|
|
6057
6125
|
try {
|
|
6058
6126
|
const projectPath = sanitizePath(input.path);
|
|
6059
6127
|
const store = await loadGraphStore(projectPath);
|
|
6060
|
-
const { ConflictPredictor } = await import("./dist-
|
|
6128
|
+
const { ConflictPredictor } = await import("./dist-DWBTZXR5.js");
|
|
6061
6129
|
const predictor = new ConflictPredictor(store ?? void 0);
|
|
6062
6130
|
const result = predictor.predict({
|
|
6063
6131
|
tasks: input.tasks,
|
|
@@ -6163,7 +6231,7 @@ async function handleDetectStaleConstraints(input) {
|
|
|
6163
6231
|
isError: true
|
|
6164
6232
|
};
|
|
6165
6233
|
}
|
|
6166
|
-
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-
|
|
6234
|
+
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-STFGYKZH.js");
|
|
6167
6235
|
const store = await loadGraphStore2(projectPath);
|
|
6168
6236
|
if (!store) {
|
|
6169
6237
|
return {
|
|
@@ -6184,7 +6252,7 @@ async function handleDetectStaleConstraints(input) {
|
|
|
6184
6252
|
]
|
|
6185
6253
|
};
|
|
6186
6254
|
}
|
|
6187
|
-
const { detectStaleConstraints } = await import("./dist-
|
|
6255
|
+
const { detectStaleConstraints } = await import("./dist-6D2F2J57.js");
|
|
6188
6256
|
const result = detectStaleConstraints(
|
|
6189
6257
|
store,
|
|
6190
6258
|
windowDays,
|
|
@@ -6215,6 +6283,7 @@ async function handleDetectStaleConstraints(input) {
|
|
|
6215
6283
|
init_dist2();
|
|
6216
6284
|
|
|
6217
6285
|
// src/skill/health-snapshot.ts
|
|
6286
|
+
init_dist2();
|
|
6218
6287
|
import { execSync } from "child_process";
|
|
6219
6288
|
import * as fs16 from "fs";
|
|
6220
6289
|
import * as path19 from "path";
|
|
@@ -6314,10 +6383,10 @@ function countCriticalFindings(securityData) {
|
|
|
6314
6383
|
return findings.filter((f) => f.severity === "error").length;
|
|
6315
6384
|
}
|
|
6316
6385
|
async function runHealthChecks(projectPath) {
|
|
6317
|
-
const { handleAssessProject: handleAssessProject2 } = await import("./assess-project-
|
|
6318
|
-
const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-
|
|
6319
|
-
const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-
|
|
6320
|
-
const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-
|
|
6386
|
+
const { handleAssessProject: handleAssessProject2 } = await import("./assess-project-KOTAPAHD.js");
|
|
6387
|
+
const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-YQH2NYXY.js");
|
|
6388
|
+
const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-DBW2INZU.js");
|
|
6389
|
+
const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-4W2P5ZGI.js");
|
|
6321
6390
|
const [assessResult, depsResult, entropyResult, securityResult] = await Promise.all([
|
|
6322
6391
|
handleAssessProject2({
|
|
6323
6392
|
path: projectPath,
|
|
@@ -6374,10 +6443,10 @@ function avg(values) {
|
|
|
6374
6443
|
}
|
|
6375
6444
|
async function runGraphMetrics(projectPath) {
|
|
6376
6445
|
try {
|
|
6377
|
-
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-
|
|
6446
|
+
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-STFGYKZH.js");
|
|
6378
6447
|
const store = await loadGraphStore2(projectPath);
|
|
6379
6448
|
if (!store) return ZERO_METRICS;
|
|
6380
|
-
const { GraphCouplingAdapter, GraphComplexityAdapter, GraphAnomalyAdapter } = await import("./dist-
|
|
6449
|
+
const { GraphCouplingAdapter, GraphComplexityAdapter, GraphAnomalyAdapter } = await import("./dist-DWBTZXR5.js");
|
|
6381
6450
|
const couplingAdapter = new GraphCouplingAdapter(store);
|
|
6382
6451
|
const couplingData = couplingAdapter.computeCouplingData();
|
|
6383
6452
|
const files = couplingData.files;
|
|
@@ -6421,11 +6490,12 @@ async function captureHealthSnapshot(projectPath) {
|
|
|
6421
6490
|
runGraphMetrics(projectPath)
|
|
6422
6491
|
]);
|
|
6423
6492
|
const signals = deriveSignals(checks, metrics);
|
|
6493
|
+
const reconciledChecks = reconcilePassed(checks, signals);
|
|
6424
6494
|
const snapshot = {
|
|
6425
6495
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6426
6496
|
gitHead,
|
|
6427
6497
|
projectPath,
|
|
6428
|
-
checks,
|
|
6498
|
+
checks: reconciledChecks,
|
|
6429
6499
|
metrics,
|
|
6430
6500
|
signals
|
|
6431
6501
|
};
|
|
@@ -6793,17 +6863,7 @@ function recommend(snapshot, skills, options = {}) {
|
|
|
6793
6863
|
}
|
|
6794
6864
|
|
|
6795
6865
|
// src/skill/dispatch-engine.ts
|
|
6796
|
-
var SIGNAL_CATEGORIES =
|
|
6797
|
-
"circular-deps": "structure",
|
|
6798
|
-
"layer-violations": "structure",
|
|
6799
|
-
"high-coupling": "structure",
|
|
6800
|
-
"dead-code": "quality",
|
|
6801
|
-
drift: "quality",
|
|
6802
|
-
"doc-gaps": "quality",
|
|
6803
|
-
"security-findings": "security",
|
|
6804
|
-
"perf-regression": "performance",
|
|
6805
|
-
"low-coverage": "coverage"
|
|
6806
|
-
};
|
|
6866
|
+
var SIGNAL_CATEGORIES = SIGNAL_CATEGORY_MAP;
|
|
6807
6867
|
function getSignalCategory(signal) {
|
|
6808
6868
|
return SIGNAL_CATEGORIES[signal] ?? null;
|
|
6809
6869
|
}
|
|
@@ -7073,7 +7133,7 @@ async function handleDispatchSkills(input) {
|
|
|
7073
7133
|
let skillTriggers;
|
|
7074
7134
|
if (trigger) {
|
|
7075
7135
|
try {
|
|
7076
|
-
const { loadOrRebuildIndex: loadOrRebuildIndex2 } = await import("./index-builder-
|
|
7136
|
+
const { loadOrRebuildIndex: loadOrRebuildIndex2 } = await import("./index-builder-52IJJHJC.js");
|
|
7077
7137
|
const index = loadOrRebuildIndex2("claude-code", projectRoot);
|
|
7078
7138
|
skillTriggers = /* @__PURE__ */ new Map();
|
|
7079
7139
|
for (const [name, entry] of Object.entries(index.skills)) {
|
|
@@ -7160,7 +7220,7 @@ async function handleGetDecayTrends(input) {
|
|
|
7160
7220
|
};
|
|
7161
7221
|
}
|
|
7162
7222
|
try {
|
|
7163
|
-
const core = await import("./dist-
|
|
7223
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
7164
7224
|
const { TimelineManager } = core;
|
|
7165
7225
|
const manager = new TimelineManager(projectPath);
|
|
7166
7226
|
const timeline = manager.load();
|
|
@@ -7295,7 +7355,7 @@ async function handleCodeOutline(input) {
|
|
|
7295
7355
|
};
|
|
7296
7356
|
}
|
|
7297
7357
|
try {
|
|
7298
|
-
const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-
|
|
7358
|
+
const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-6D2F2J57.js");
|
|
7299
7359
|
const { stat } = await import("fs/promises");
|
|
7300
7360
|
const stats = await stat(targetPath).catch(() => null);
|
|
7301
7361
|
if (stats?.isFile()) {
|
|
@@ -7314,7 +7374,7 @@ async function handleCodeOutline(input) {
|
|
|
7314
7374
|
})
|
|
7315
7375
|
);
|
|
7316
7376
|
fileStats.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
7317
|
-
const { paginate: paginate2 } = await import("./dist-
|
|
7377
|
+
const { paginate: paginate2 } = await import("./dist-6D2F2J57.js");
|
|
7318
7378
|
const paged = paginate2(fileStats, input.offset ?? 0, input.limit ?? 30);
|
|
7319
7379
|
const results = [];
|
|
7320
7380
|
for (const entry of paged.items) {
|
|
@@ -7387,7 +7447,7 @@ async function handleCodeSearch(input) {
|
|
|
7387
7447
|
};
|
|
7388
7448
|
}
|
|
7389
7449
|
try {
|
|
7390
|
-
const { searchSymbols } = await import("./dist-
|
|
7450
|
+
const { searchSymbols } = await import("./dist-6D2F2J57.js");
|
|
7391
7451
|
const result = await searchSymbols(input.query, directory, input.glob);
|
|
7392
7452
|
const lines = [`Search: "${result.query}" \u2014 ${result.matches.length} matches`];
|
|
7393
7453
|
for (const match of result.matches) {
|
|
@@ -7456,7 +7516,7 @@ async function handleCodeUnfold(input) {
|
|
|
7456
7516
|
}
|
|
7457
7517
|
try {
|
|
7458
7518
|
if (input.symbol) {
|
|
7459
|
-
const { unfoldSymbol } = await import("./dist-
|
|
7519
|
+
const { unfoldSymbol } = await import("./dist-6D2F2J57.js");
|
|
7460
7520
|
const result = await unfoldSymbol(filePath, input.symbol);
|
|
7461
7521
|
const header = result.warning ? `${result.file}:${result.startLine}-${result.endLine} ${result.warning}
|
|
7462
7522
|
` : `${result.file}:${result.startLine}-${result.endLine}
|
|
@@ -7464,7 +7524,7 @@ async function handleCodeUnfold(input) {
|
|
|
7464
7524
|
return { content: [{ type: "text", text: header + result.content }] };
|
|
7465
7525
|
}
|
|
7466
7526
|
if (input.startLine != null && input.endLine != null) {
|
|
7467
|
-
const { unfoldRange } = await import("./dist-
|
|
7527
|
+
const { unfoldRange } = await import("./dist-6D2F2J57.js");
|
|
7468
7528
|
const result = await unfoldRange(filePath, input.startLine, input.endLine);
|
|
7469
7529
|
const header = `${result.file}:${result.startLine}-${result.endLine}
|
|
7470
7530
|
`;
|
|
@@ -7526,7 +7586,7 @@ async function handleCheckTraceability(input) {
|
|
|
7526
7586
|
isError: true
|
|
7527
7587
|
};
|
|
7528
7588
|
}
|
|
7529
|
-
const { queryTraceability } = await import("./dist-
|
|
7589
|
+
const { queryTraceability } = await import("./dist-DWBTZXR5.js");
|
|
7530
7590
|
const options = {};
|
|
7531
7591
|
if (input.spec) options.specPath = input.spec;
|
|
7532
7592
|
if (input.feature) options.featureName = input.feature;
|
|
@@ -7649,7 +7709,7 @@ async function handlePredictFailures(input) {
|
|
|
7649
7709
|
};
|
|
7650
7710
|
}
|
|
7651
7711
|
try {
|
|
7652
|
-
const core = await import("./dist-
|
|
7712
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
7653
7713
|
const { TimelineManager, PredictionEngine, SpecImpactEstimator } = core;
|
|
7654
7714
|
const manager = new TimelineManager(projectPath);
|
|
7655
7715
|
const includeRoadmap = input.includeRoadmap !== false;
|
|
@@ -7660,7 +7720,7 @@ async function handlePredictFailures(input) {
|
|
|
7660
7720
|
};
|
|
7661
7721
|
if (input.horizon !== void 0) opts.horizon = input.horizon;
|
|
7662
7722
|
if (input.category) opts.categories = [input.category];
|
|
7663
|
-
const result = engine.predict(opts);
|
|
7723
|
+
const result = await engine.predict(opts);
|
|
7664
7724
|
return {
|
|
7665
7725
|
content: [
|
|
7666
7726
|
{
|
|
@@ -8532,7 +8592,7 @@ function handleContentMode(content, pipeline, budget, source) {
|
|
|
8532
8592
|
};
|
|
8533
8593
|
}
|
|
8534
8594
|
async function handleIntentMode(projectPath, intent, pipeline, budget, filterContent) {
|
|
8535
|
-
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-
|
|
8595
|
+
const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-STFGYKZH.js");
|
|
8536
8596
|
const store = await loadGraphStore2(projectPath);
|
|
8537
8597
|
if (!store) {
|
|
8538
8598
|
return {
|
|
@@ -8545,7 +8605,7 @@ async function handleIntentMode(projectPath, intent, pipeline, budget, filterCon
|
|
|
8545
8605
|
isError: true
|
|
8546
8606
|
};
|
|
8547
8607
|
}
|
|
8548
|
-
const { PackedSummaryCache, FusionLayer, ContextQL } = await import("./dist-
|
|
8608
|
+
const { PackedSummaryCache, FusionLayer, ContextQL } = await import("./dist-DWBTZXR5.js");
|
|
8549
8609
|
const cache = new PackedSummaryCache(store);
|
|
8550
8610
|
const cachedEnvelope = cache.get(intent);
|
|
8551
8611
|
if (cachedEnvelope) {
|
|
@@ -8758,7 +8818,7 @@ async function handleDetectConstraintEmergence(input) {
|
|
|
8758
8818
|
}
|
|
8759
8819
|
const nodePath = await import("path");
|
|
8760
8820
|
const historyPath = nodePath.resolve(projectPath, ".harness/arch/violation-history.json");
|
|
8761
|
-
const { ViolationHistoryManager, detectEmergentConstraints } = await import("./dist-
|
|
8821
|
+
const { ViolationHistoryManager, detectEmergentConstraints } = await import("./dist-6D2F2J57.js");
|
|
8762
8822
|
const manager = new ViolationHistoryManager(historyPath);
|
|
8763
8823
|
const history = manager.load();
|
|
8764
8824
|
const options = { windowWeeks, minOccurrences };
|
|
@@ -8846,7 +8906,7 @@ async function handleRunCIChecks(input) {
|
|
|
8846
8906
|
};
|
|
8847
8907
|
}
|
|
8848
8908
|
try {
|
|
8849
|
-
const { runCIChecks } = await import("./dist-
|
|
8909
|
+
const { runCIChecks } = await import("./dist-6D2F2J57.js");
|
|
8850
8910
|
const skip = input.checks ? VALID_CHECKS.filter((c) => !input.checks.includes(c)) : [];
|
|
8851
8911
|
const result = await runCIChecks({
|
|
8852
8912
|
projectRoot: projectPath,
|
|
@@ -8908,7 +8968,7 @@ async function handleGenerateBlueprint(input) {
|
|
|
8908
8968
|
};
|
|
8909
8969
|
}
|
|
8910
8970
|
try {
|
|
8911
|
-
const { ProjectScanner } = await import("./dist-
|
|
8971
|
+
const { ProjectScanner } = await import("./dist-6D2F2J57.js");
|
|
8912
8972
|
const scanner = new ProjectScanner(projectPath);
|
|
8913
8973
|
const data = await scanner.scan();
|
|
8914
8974
|
return {
|
|
@@ -9076,45 +9136,68 @@ var emitSkillProposalDefinition = {
|
|
|
9076
9136
|
required: ["path", "kind", "proposedBy", "justification", "content"]
|
|
9077
9137
|
}
|
|
9078
9138
|
};
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
return
|
|
9139
|
+
function validateNewSkillProposal(input) {
|
|
9140
|
+
if (!input.content.skillYaml || !input.content.skillMd) {
|
|
9141
|
+
return "new-skill proposals require both skillYaml and skillMd";
|
|
9142
|
+
}
|
|
9143
|
+
if (input.targetSkill) {
|
|
9144
|
+
return "targetSkill is forbidden on new-skill proposals";
|
|
9145
|
+
}
|
|
9146
|
+
if (input.content.diff) {
|
|
9147
|
+
return "diff is forbidden on new-skill proposals (use skillYaml/skillMd)";
|
|
9148
|
+
}
|
|
9149
|
+
return null;
|
|
9150
|
+
}
|
|
9151
|
+
function validateRefinementProposal(input) {
|
|
9152
|
+
if (!input.targetSkill) {
|
|
9153
|
+
return "refinement proposals require targetSkill";
|
|
9085
9154
|
}
|
|
9155
|
+
if (!input.content.diff) {
|
|
9156
|
+
return "refinement proposals require a unified diff";
|
|
9157
|
+
}
|
|
9158
|
+
if (input.content.skillYaml || input.content.skillMd) {
|
|
9159
|
+
return "skillYaml/skillMd are forbidden on refinement proposals (encode in diff)";
|
|
9160
|
+
}
|
|
9161
|
+
return null;
|
|
9162
|
+
}
|
|
9163
|
+
function validateProposalCrossFields(input) {
|
|
9086
9164
|
if (input.kind === "new-skill") {
|
|
9087
|
-
|
|
9088
|
-
return mcpError("new-skill proposals require both skillYaml and skillMd");
|
|
9089
|
-
}
|
|
9090
|
-
if (input.targetSkill) {
|
|
9091
|
-
return mcpError("targetSkill is forbidden on new-skill proposals");
|
|
9092
|
-
}
|
|
9093
|
-
if (input.content.diff) {
|
|
9094
|
-
return mcpError("diff is forbidden on new-skill proposals (use skillYaml/skillMd)");
|
|
9095
|
-
}
|
|
9096
|
-
} else if (input.kind === "refinement") {
|
|
9097
|
-
if (!input.targetSkill) {
|
|
9098
|
-
return mcpError("refinement proposals require targetSkill");
|
|
9099
|
-
}
|
|
9100
|
-
if (!input.content.diff) {
|
|
9101
|
-
return mcpError("refinement proposals require a unified diff");
|
|
9102
|
-
}
|
|
9103
|
-
if (input.content.skillYaml || input.content.skillMd) {
|
|
9104
|
-
return mcpError("skillYaml/skillMd are forbidden on refinement proposals (encode in diff)");
|
|
9105
|
-
}
|
|
9165
|
+
return validateNewSkillProposal(input);
|
|
9106
9166
|
}
|
|
9107
|
-
|
|
9108
|
-
|
|
9167
|
+
if (input.kind === "refinement") {
|
|
9168
|
+
return validateRefinementProposal(input);
|
|
9169
|
+
}
|
|
9170
|
+
return null;
|
|
9171
|
+
}
|
|
9172
|
+
async function loadCoreProposalApi() {
|
|
9109
9173
|
try {
|
|
9110
|
-
const core = await import("./dist-
|
|
9111
|
-
|
|
9112
|
-
|
|
9174
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
9175
|
+
return {
|
|
9176
|
+
createProposal: core.createProposal,
|
|
9177
|
+
ProposalConflictError: core.ProposalConflictError
|
|
9178
|
+
};
|
|
9113
9179
|
} catch (err2) {
|
|
9114
9180
|
return mcpError(
|
|
9115
9181
|
`Failed to load proposal store: ${err2 instanceof Error ? err2.message : "unknown error"}`
|
|
9116
9182
|
);
|
|
9117
9183
|
}
|
|
9184
|
+
}
|
|
9185
|
+
async function handleEmitSkillProposal(input) {
|
|
9186
|
+
let projectPath;
|
|
9187
|
+
try {
|
|
9188
|
+
projectPath = sanitizePath(input.path);
|
|
9189
|
+
} catch (err2) {
|
|
9190
|
+
return mcpError(err2 instanceof Error ? err2.message : "Invalid path");
|
|
9191
|
+
}
|
|
9192
|
+
const validationError = validateProposalCrossFields(input);
|
|
9193
|
+
if (validationError) {
|
|
9194
|
+
return mcpError(validationError);
|
|
9195
|
+
}
|
|
9196
|
+
const core = await loadCoreProposalApi();
|
|
9197
|
+
if ("content" in core) {
|
|
9198
|
+
return core;
|
|
9199
|
+
}
|
|
9200
|
+
const { createProposal, ProposalConflictError } = core;
|
|
9118
9201
|
try {
|
|
9119
9202
|
const proposal = await createProposal(projectPath, {
|
|
9120
9203
|
kind: input.kind,
|
|
@@ -9150,8 +9233,8 @@ async function handleEmitSkillProposal(input) {
|
|
|
9150
9233
|
}
|
|
9151
9234
|
|
|
9152
9235
|
// src/mcp/tools/audit-anatomy.ts
|
|
9153
|
-
import * as
|
|
9154
|
-
import * as
|
|
9236
|
+
import * as fs22 from "fs";
|
|
9237
|
+
import * as path24 from "path";
|
|
9155
9238
|
|
|
9156
9239
|
// src/audit/component-anatomy/parsers/ast.ts
|
|
9157
9240
|
import * as fs18 from "fs";
|
|
@@ -9260,6 +9343,10 @@ function findTypeDeclaration(sourceFile, typeName) {
|
|
|
9260
9343
|
return null;
|
|
9261
9344
|
}
|
|
9262
9345
|
|
|
9346
|
+
// src/audit/component-anatomy/resolvers/component-type.ts
|
|
9347
|
+
import * as fs20 from "fs";
|
|
9348
|
+
import * as path23 from "path";
|
|
9349
|
+
|
|
9263
9350
|
// src/audit/component-anatomy/catalog/conventions/button.ts
|
|
9264
9351
|
var buttonConvention = {
|
|
9265
9352
|
componentType: "Button",
|
|
@@ -9843,6 +9930,65 @@ function lookupConvention(componentType) {
|
|
|
9843
9930
|
return conventionByType.get(componentType) ?? null;
|
|
9844
9931
|
}
|
|
9845
9932
|
|
|
9933
|
+
// src/audit/component-anatomy/parsers/jsdoc.ts
|
|
9934
|
+
function extractLeadingJsDoc(source) {
|
|
9935
|
+
const re = /^\s*(?:#![^\n]*\n)?\s*(?:['"]use (?:client|server)['"];?\s*)?\s*\/\*\*([\s\S]*?)\*\//;
|
|
9936
|
+
const match = re.exec(source);
|
|
9937
|
+
if (!match) return null;
|
|
9938
|
+
return match[1].split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
|
|
9939
|
+
}
|
|
9940
|
+
function readJsDocTag(jsdoc, tag) {
|
|
9941
|
+
const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9942
|
+
const re = new RegExp(`@${escaped}\\b[ \\t]*(.*)$`, "gm");
|
|
9943
|
+
const values = [];
|
|
9944
|
+
for (const m of jsdoc.matchAll(re)) {
|
|
9945
|
+
values.push((m[1] ?? "").trim());
|
|
9946
|
+
}
|
|
9947
|
+
return values;
|
|
9948
|
+
}
|
|
9949
|
+
function readJsDocTagValue(jsdoc, tag) {
|
|
9950
|
+
const [first] = readJsDocTag(jsdoc, tag);
|
|
9951
|
+
return first ? first : null;
|
|
9952
|
+
}
|
|
9953
|
+
|
|
9954
|
+
// src/audit/component-anatomy/parsers/design-registry.ts
|
|
9955
|
+
import * as fs19 from "fs";
|
|
9956
|
+
import * as path22 from "path";
|
|
9957
|
+
function parseComponentRegistry(designMd) {
|
|
9958
|
+
const lines = designMd.split("\n");
|
|
9959
|
+
const headingIdx = lines.findIndex((l) => /^#{1,6}\s+Component Registry\b/i.test(l));
|
|
9960
|
+
if (headingIdx === -1) return [];
|
|
9961
|
+
const entries = [];
|
|
9962
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
9963
|
+
const line = lines[i];
|
|
9964
|
+
if (/^#{1,6}\s+/.test(line)) break;
|
|
9965
|
+
const trimmed = line.trim();
|
|
9966
|
+
if (!trimmed.startsWith("|")) continue;
|
|
9967
|
+
const cells = trimmed.slice(1, trimmed.endsWith("|") ? -1 : void 0).split("|").map((c) => c.trim());
|
|
9968
|
+
const [type, file] = cells;
|
|
9969
|
+
if (!type || !file) continue;
|
|
9970
|
+
if (/^type$/i.test(type) || /^:?-{2,}:?$/.test(type)) continue;
|
|
9971
|
+
entries.push({ type, file });
|
|
9972
|
+
}
|
|
9973
|
+
return entries;
|
|
9974
|
+
}
|
|
9975
|
+
function findDesignMd(fromPath) {
|
|
9976
|
+
let dir = path22.dirname(path22.resolve(fromPath));
|
|
9977
|
+
for (; ; ) {
|
|
9978
|
+
let names;
|
|
9979
|
+
try {
|
|
9980
|
+
names = fs19.readdirSync(dir);
|
|
9981
|
+
} catch {
|
|
9982
|
+
names = [];
|
|
9983
|
+
}
|
|
9984
|
+
const hit = names.find((n) => n.toLowerCase() === "design.md");
|
|
9985
|
+
if (hit) return path22.join(dir, hit);
|
|
9986
|
+
const parent = path22.dirname(dir);
|
|
9987
|
+
if (parent === dir) return null;
|
|
9988
|
+
dir = parent;
|
|
9989
|
+
}
|
|
9990
|
+
}
|
|
9991
|
+
|
|
9846
9992
|
// src/audit/component-anatomy/resolvers/component-type.ts
|
|
9847
9993
|
var componentTypeSet = new Set(getCatalogTypes());
|
|
9848
9994
|
function resolveComponentType(filePath, fileContents) {
|
|
@@ -9852,11 +9998,31 @@ function resolveComponentType(filePath, fileContents) {
|
|
|
9852
9998
|
if (registryType !== null) return registryType;
|
|
9853
9999
|
return resolveFromExportName(fileContents);
|
|
9854
10000
|
}
|
|
9855
|
-
function resolveFromJSDoc(_filePath,
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
10001
|
+
function resolveFromJSDoc(_filePath, fileContents) {
|
|
10002
|
+
const jsdoc = extractLeadingJsDoc(fileContents);
|
|
10003
|
+
if (jsdoc === null) return null;
|
|
10004
|
+
return readJsDocTagValue(jsdoc, "component-type");
|
|
10005
|
+
}
|
|
10006
|
+
var registryCache = /* @__PURE__ */ new Map();
|
|
10007
|
+
function resolveFromDesignRegistry(filePath) {
|
|
10008
|
+
const designPath = findDesignMd(filePath);
|
|
10009
|
+
if (designPath === null) return null;
|
|
10010
|
+
let byFile = registryCache.get(designPath);
|
|
10011
|
+
if (!byFile) {
|
|
10012
|
+
byFile = /* @__PURE__ */ new Map();
|
|
10013
|
+
let contents;
|
|
10014
|
+
try {
|
|
10015
|
+
contents = fs20.readFileSync(designPath, "utf8");
|
|
10016
|
+
} catch {
|
|
10017
|
+
contents = "";
|
|
10018
|
+
}
|
|
10019
|
+
const designDir = path23.dirname(designPath);
|
|
10020
|
+
for (const entry of parseComponentRegistry(contents)) {
|
|
10021
|
+
byFile.set(path23.resolve(designDir, entry.file), entry.type);
|
|
10022
|
+
}
|
|
10023
|
+
registryCache.set(designPath, byFile);
|
|
10024
|
+
}
|
|
10025
|
+
return byFile.get(path23.resolve(filePath)) ?? null;
|
|
9860
10026
|
}
|
|
9861
10027
|
function resolveFromExportName(fileContents) {
|
|
9862
10028
|
const patterns = [
|
|
@@ -9875,20 +10041,142 @@ function resolveFromExportName(fileContents) {
|
|
|
9875
10041
|
return null;
|
|
9876
10042
|
}
|
|
9877
10043
|
|
|
10044
|
+
// src/audit/component-anatomy/resolvers/source-of-truth.ts
|
|
10045
|
+
import * as fs21 from "fs";
|
|
10046
|
+
|
|
10047
|
+
// src/audit/component-anatomy/parsers/anatomy-tags.ts
|
|
10048
|
+
var JSDOC_SOURCE_REF = "design-component-anatomy/jsdoc";
|
|
10049
|
+
function fixHint(name, axis) {
|
|
10050
|
+
return `Add the \`${name}\` ${axis} to this component, or drop the \`@anatomy-${axis} ${name}\` tag if it no longer applies.`;
|
|
10051
|
+
}
|
|
10052
|
+
function parseFlaggedPart(value, axis) {
|
|
10053
|
+
const tokens = value.split(/\s+/).filter(Boolean);
|
|
10054
|
+
const name = tokens[0];
|
|
10055
|
+
if (!name) return null;
|
|
10056
|
+
const flags = new Set(tokens.slice(1).map((t) => t.toLowerCase()));
|
|
10057
|
+
return {
|
|
10058
|
+
name,
|
|
10059
|
+
required: flags.has("required"),
|
|
10060
|
+
...flags.has("exclusive") ? { exclusive: true } : {},
|
|
10061
|
+
fixHint: fixHint(name, axis)
|
|
10062
|
+
};
|
|
10063
|
+
}
|
|
10064
|
+
function parsePipeList(value, axis) {
|
|
10065
|
+
return value.split("|").map((n) => n.trim()).filter(Boolean).map((name) => ({ name, required: false, fixHint: fixHint(name, axis) }));
|
|
10066
|
+
}
|
|
10067
|
+
function buildAnatomyRuleFromJsDoc(jsdoc, componentType) {
|
|
10068
|
+
const slots = readJsDocTag(jsdoc, "anatomy-slot").map((v) => parseFlaggedPart(v, "slot")).filter((p) => p !== null);
|
|
10069
|
+
const states = readJsDocTag(jsdoc, "anatomy-state").map((v) => parseFlaggedPart(v, "state")).filter((p) => p !== null);
|
|
10070
|
+
const variants = readJsDocTag(jsdoc, "anatomy-variant").flatMap(
|
|
10071
|
+
(v) => parsePipeList(v, "variant")
|
|
10072
|
+
);
|
|
10073
|
+
const sizes = readJsDocTag(jsdoc, "anatomy-size").flatMap((v) => parsePipeList(v, "size"));
|
|
10074
|
+
if (slots.length === 0 && states.length === 0 && variants.length === 0 && sizes.length === 0) {
|
|
10075
|
+
return null;
|
|
10076
|
+
}
|
|
10077
|
+
return { componentType, slots, states, variants, sizes, source: { ref: JSDOC_SOURCE_REF } };
|
|
10078
|
+
}
|
|
10079
|
+
|
|
10080
|
+
// src/audit/component-anatomy/parsers/design-overrides.ts
|
|
10081
|
+
var SINGULAR = { slots: "slot", states: "state", variants: "variant", sizes: "size" };
|
|
10082
|
+
var SOURCE_REF = "design-component-anatomy/design-md";
|
|
10083
|
+
function partFrom(raw, axis) {
|
|
10084
|
+
const m = /^([A-Za-z0-9][A-Za-z0-9-]*)\s*(?:\(([^)]*)\))?/.exec(raw.trim());
|
|
10085
|
+
const name = m?.[1];
|
|
10086
|
+
if (!name) return null;
|
|
10087
|
+
const flags = (m?.[2] ?? "").toLowerCase();
|
|
10088
|
+
return {
|
|
10089
|
+
name,
|
|
10090
|
+
required: /\brequired\b/.test(flags),
|
|
10091
|
+
.../\bexclusive\b/.test(flags) ? { exclusive: true } : {},
|
|
10092
|
+
fixHint: `Add the \`${name}\` ${SINGULAR[axis]} to this component (declared in the DESIGN.md anatomy override).`
|
|
10093
|
+
};
|
|
10094
|
+
}
|
|
10095
|
+
function parseAnatomyOverrides(designMd) {
|
|
10096
|
+
const lines = designMd.split("\n");
|
|
10097
|
+
const out = /* @__PURE__ */ new Map();
|
|
10098
|
+
let i = lines.findIndex((l) => /^#{1,6}\s+Component Anatomy Overrides\b/i.test(l));
|
|
10099
|
+
if (i === -1) return out;
|
|
10100
|
+
let current = null;
|
|
10101
|
+
let currentAxis = null;
|
|
10102
|
+
const flush = () => {
|
|
10103
|
+
if (!current) return;
|
|
10104
|
+
out.set(current.type, {
|
|
10105
|
+
componentType: current.type,
|
|
10106
|
+
slots: current.axes.slots,
|
|
10107
|
+
states: current.axes.states,
|
|
10108
|
+
variants: current.axes.variants,
|
|
10109
|
+
sizes: current.axes.sizes,
|
|
10110
|
+
source: { ref: SOURCE_REF }
|
|
10111
|
+
});
|
|
10112
|
+
};
|
|
10113
|
+
for (i += 1; i < lines.length; i++) {
|
|
10114
|
+
const line = lines[i];
|
|
10115
|
+
if (/^#{1,2}\s/.test(line)) break;
|
|
10116
|
+
const compHeading = /^#{3,6}\s+(.+?)\s*$/.exec(line);
|
|
10117
|
+
if (compHeading) {
|
|
10118
|
+
flush();
|
|
10119
|
+
current = {
|
|
10120
|
+
type: compHeading[1].trim(),
|
|
10121
|
+
axes: { slots: [], states: [], variants: [], sizes: [] }
|
|
10122
|
+
};
|
|
10123
|
+
currentAxis = null;
|
|
10124
|
+
continue;
|
|
10125
|
+
}
|
|
10126
|
+
if (!current) continue;
|
|
10127
|
+
const axisMatch = /^\s*(slots|states|variants|sizes)\s*:\s*(.*)$/i.exec(line);
|
|
10128
|
+
if (axisMatch) {
|
|
10129
|
+
currentAxis = axisMatch[1].toLowerCase();
|
|
10130
|
+
const inline = axisMatch[2].trim();
|
|
10131
|
+
if (inline) {
|
|
10132
|
+
for (const tok of inline.split(",")) {
|
|
10133
|
+
const part = partFrom(tok, currentAxis);
|
|
10134
|
+
if (part) current.axes[currentAxis].push(part);
|
|
10135
|
+
}
|
|
10136
|
+
currentAxis = null;
|
|
10137
|
+
}
|
|
10138
|
+
continue;
|
|
10139
|
+
}
|
|
10140
|
+
const listItem = /^\s*[-*]\s+(.*)$/.exec(line);
|
|
10141
|
+
if (listItem && currentAxis) {
|
|
10142
|
+
const part = partFrom(listItem[1], currentAxis);
|
|
10143
|
+
if (part) current.axes[currentAxis].push(part);
|
|
10144
|
+
}
|
|
10145
|
+
}
|
|
10146
|
+
flush();
|
|
10147
|
+
return out;
|
|
10148
|
+
}
|
|
10149
|
+
|
|
9878
10150
|
// src/audit/component-anatomy/resolvers/source-of-truth.ts
|
|
9879
10151
|
function resolveAnatomyRules(filePath, fileContents, componentType) {
|
|
9880
10152
|
if (componentType === null) return null;
|
|
9881
|
-
const jsdocRule = resolveFromJSDoc2(
|
|
10153
|
+
const jsdocRule = resolveFromJSDoc2(fileContents, componentType);
|
|
9882
10154
|
if (jsdocRule !== null) return jsdocRule;
|
|
9883
10155
|
const overrideRule = resolveFromDesignOverrides(filePath, componentType);
|
|
9884
10156
|
if (overrideRule !== null) return overrideRule;
|
|
9885
10157
|
return lookupConvention(componentType);
|
|
9886
10158
|
}
|
|
9887
|
-
function resolveFromJSDoc2(
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
10159
|
+
function resolveFromJSDoc2(fileContents, componentType) {
|
|
10160
|
+
const jsdoc = extractLeadingJsDoc(fileContents);
|
|
10161
|
+
if (jsdoc === null) return null;
|
|
10162
|
+
return buildAnatomyRuleFromJsDoc(jsdoc, componentType);
|
|
10163
|
+
}
|
|
10164
|
+
var overridesCache = /* @__PURE__ */ new Map();
|
|
10165
|
+
function resolveFromDesignOverrides(filePath, componentType) {
|
|
10166
|
+
const designPath = findDesignMd(filePath);
|
|
10167
|
+
if (designPath === null) return null;
|
|
10168
|
+
let byType = overridesCache.get(designPath);
|
|
10169
|
+
if (!byType) {
|
|
10170
|
+
let contents;
|
|
10171
|
+
try {
|
|
10172
|
+
contents = fs21.readFileSync(designPath, "utf8");
|
|
10173
|
+
} catch {
|
|
10174
|
+
contents = "";
|
|
10175
|
+
}
|
|
10176
|
+
byType = parseAnatomyOverrides(contents);
|
|
10177
|
+
overridesCache.set(designPath, byType);
|
|
10178
|
+
}
|
|
10179
|
+
return byType.get(componentType) ?? null;
|
|
9892
10180
|
}
|
|
9893
10181
|
|
|
9894
10182
|
// src/audit/component-anatomy/findings/severity.ts
|
|
@@ -10007,10 +10295,199 @@ function runConventionRule(rule, parsed, options) {
|
|
|
10007
10295
|
return findings;
|
|
10008
10296
|
}
|
|
10009
10297
|
|
|
10298
|
+
// src/audit/component-anatomy/catalog/patterns/index.ts
|
|
10299
|
+
function lineAt(contents, index) {
|
|
10300
|
+
let line = 1;
|
|
10301
|
+
for (let i = 0; i < index && i < contents.length; i++) {
|
|
10302
|
+
if (contents[i] === "\n") line += 1;
|
|
10303
|
+
}
|
|
10304
|
+
return line;
|
|
10305
|
+
}
|
|
10306
|
+
var anyMatch = (contents, res) => res.some((re) => re.test(contents));
|
|
10307
|
+
function firstTrigger(contents, triggers) {
|
|
10308
|
+
let best = -1;
|
|
10309
|
+
for (const re of triggers) {
|
|
10310
|
+
const m = re.exec(contents);
|
|
10311
|
+
if (m && (best === -1 || m.index < best)) best = m.index;
|
|
10312
|
+
}
|
|
10313
|
+
return best;
|
|
10314
|
+
}
|
|
10315
|
+
function buildPresenceFinding(spec, file, contents, componentType, index) {
|
|
10316
|
+
const line = lineAt(contents, index);
|
|
10317
|
+
const snippet = (contents.split("\n")[line - 1] ?? "").trim();
|
|
10318
|
+
return {
|
|
10319
|
+
code: spec.code,
|
|
10320
|
+
severity: "warn",
|
|
10321
|
+
file,
|
|
10322
|
+
line,
|
|
10323
|
+
componentType,
|
|
10324
|
+
message: spec.message,
|
|
10325
|
+
evidence: { snippet },
|
|
10326
|
+
rule: { id: spec.code, source: `design-component-anatomy/pattern-${spec.id}` },
|
|
10327
|
+
fix: { kind: "manual", description: spec.fix }
|
|
10328
|
+
};
|
|
10329
|
+
}
|
|
10330
|
+
function detectPresence(spec, file, contents, componentType) {
|
|
10331
|
+
const index = firstTrigger(contents, spec.triggers);
|
|
10332
|
+
if (index === -1) return [];
|
|
10333
|
+
if (anyMatch(contents, spec.mitigations)) return [];
|
|
10334
|
+
return [buildPresenceFinding(spec, file, contents, componentType, index)];
|
|
10335
|
+
}
|
|
10336
|
+
function presencePattern(spec) {
|
|
10337
|
+
return {
|
|
10338
|
+
code: spec.code,
|
|
10339
|
+
id: spec.id,
|
|
10340
|
+
source: `design-component-anatomy/pattern-${spec.id}`,
|
|
10341
|
+
detect: (file, contents, componentType) => detectPresence(spec, file, contents, componentType)
|
|
10342
|
+
};
|
|
10343
|
+
}
|
|
10344
|
+
var SPECS = [
|
|
10345
|
+
{
|
|
10346
|
+
code: "ANAT-P001",
|
|
10347
|
+
id: "map-without-empty",
|
|
10348
|
+
triggers: [/\.map\s*\(/],
|
|
10349
|
+
mitigations: [
|
|
10350
|
+
/\.length\s*===\s*0/,
|
|
10351
|
+
/\.length\s*<\s*1/,
|
|
10352
|
+
/\.length\s*>\s*0/,
|
|
10353
|
+
/\.length\s*\?/,
|
|
10354
|
+
/\blength\s*&&/,
|
|
10355
|
+
/\?\.length\b/,
|
|
10356
|
+
/\bisEmpty\b/,
|
|
10357
|
+
/\bEmptyState\b/,
|
|
10358
|
+
/\bno\s+(results|items|data|entries)\b/i
|
|
10359
|
+
],
|
|
10360
|
+
message: "List rendered with `.map(...)` but no empty state was found. An empty list renders as a blank region \u2014 add a length-zero branch (e.g. an EmptyState).",
|
|
10361
|
+
fix: "Guard the list: `items.length === 0 ? <EmptyState/> : items.map(...)`."
|
|
10362
|
+
},
|
|
10363
|
+
{
|
|
10364
|
+
code: "ANAT-P002",
|
|
10365
|
+
id: "fetch-without-loading",
|
|
10366
|
+
triggers: [/\bfetch\s*\(/, /\buse(Query|SWR|Swr|Mutation)\b/, /\baxios\s*[.(]/, /\bawait\s+/],
|
|
10367
|
+
mitigations: [
|
|
10368
|
+
/\bis?Loading\b/i,
|
|
10369
|
+
/\bisPending\b/i,
|
|
10370
|
+
/\bpending\b/i,
|
|
10371
|
+
/\bSkeleton\b/,
|
|
10372
|
+
/\bSpinner\b/,
|
|
10373
|
+
/<Suspense\b/,
|
|
10374
|
+
/\bplaceholder\b/i
|
|
10375
|
+
],
|
|
10376
|
+
message: "Async data loading found but no loading state. The surface renders empty until data resolves \u2014 add a loading boundary (skeleton, spinner, or Suspense).",
|
|
10377
|
+
fix: "Render a loading affordance while the request is in flight (e.g. `if (isLoading) return <Skeleton/>`)."
|
|
10378
|
+
},
|
|
10379
|
+
{
|
|
10380
|
+
code: "ANAT-P003",
|
|
10381
|
+
id: "fetch-without-error",
|
|
10382
|
+
triggers: [/\bfetch\s*\(/, /\buse(Query|SWR|Swr|Mutation)\b/, /\baxios\s*[.(]/, /\bawait\s+/],
|
|
10383
|
+
mitigations: [
|
|
10384
|
+
/\bis?Error\b/i,
|
|
10385
|
+
/\berror\b/i,
|
|
10386
|
+
/\bcatch\s*\(/,
|
|
10387
|
+
/\.catch\s*\(/,
|
|
10388
|
+
/\bErrorState\b/,
|
|
10389
|
+
/\bErrorBoundary\b/,
|
|
10390
|
+
/role=["']alert["']/
|
|
10391
|
+
],
|
|
10392
|
+
message: "Async data loading found but no error state. A failed request renders nothing or throws \u2014 handle and display the error case.",
|
|
10393
|
+
fix: "Add an error branch (e.g. `if (isError) return <ErrorState/>`) or a try/catch around the request."
|
|
10394
|
+
},
|
|
10395
|
+
{
|
|
10396
|
+
code: "ANAT-P004",
|
|
10397
|
+
id: "conditional-render-without-fallback",
|
|
10398
|
+
triggers: [/\{\s*[\w.]*(data|items|list|results|rows|user|profile)[\w.]*\s*&&/i],
|
|
10399
|
+
mitigations: [/\?\s*\(?</, /\?\?/, /\bEmptyState\b/, /:\s*null/, /:\s*</, /\.length\s*===\s*0/],
|
|
10400
|
+
message: "Data-driven `{value && <\u2026>}` render with no fallback. When `value` is falsy the region collapses silently \u2014 provide an explicit empty/else branch.",
|
|
10401
|
+
fix: "Use a ternary with a fallback: `value ? <View/> : <EmptyState/>`."
|
|
10402
|
+
},
|
|
10403
|
+
{
|
|
10404
|
+
code: "ANAT-P005",
|
|
10405
|
+
id: "form-without-submit-feedback",
|
|
10406
|
+
triggers: [/<form\b/i, /\bonSubmit\b/, /\bhandleSubmit\b/],
|
|
10407
|
+
mitigations: [
|
|
10408
|
+
/\bisSubmitting\b/i,
|
|
10409
|
+
/\bpending\b/i,
|
|
10410
|
+
/\bsuccess\b/i,
|
|
10411
|
+
/\berror\b/i,
|
|
10412
|
+
/\bdisabled\b/,
|
|
10413
|
+
/role=["']alert["']/,
|
|
10414
|
+
/\btoast\b/i
|
|
10415
|
+
],
|
|
10416
|
+
message: "Form submission found but no submit feedback (pending / success / error). The user gets no signal that submit worked or failed.",
|
|
10417
|
+
fix: "Disable the submit control while pending and surface a success/error state after submission."
|
|
10418
|
+
},
|
|
10419
|
+
{
|
|
10420
|
+
code: "ANAT-P006",
|
|
10421
|
+
id: "modal-without-dismiss",
|
|
10422
|
+
triggers: [/<(Modal|Dialog|Drawer|Sheet|Popover)\b/],
|
|
10423
|
+
mitigations: [/\bonClose\b/, /\bonDismiss\b/, /\bonOpenChange\b/, /\bclose\b/i, /\bonCancel\b/],
|
|
10424
|
+
message: "Modal/Dialog/Drawer used with no dismiss affordance (`onClose` / `onDismiss` / `onOpenChange`). A modal the user cannot close is a trap.",
|
|
10425
|
+
fix: "Wire a dismiss handler (`onClose` / `onOpenChange`) and a visible close control."
|
|
10426
|
+
},
|
|
10427
|
+
{
|
|
10428
|
+
code: "ANAT-P007",
|
|
10429
|
+
id: "async-action-without-pending",
|
|
10430
|
+
triggers: [
|
|
10431
|
+
/on[A-Z]\w*=\{?\s*async\b/,
|
|
10432
|
+
/\bhandle\w*\s*=\s*async\b/,
|
|
10433
|
+
/async\s+function\s+handle/i
|
|
10434
|
+
],
|
|
10435
|
+
mitigations: [
|
|
10436
|
+
/\bdisabled\b/,
|
|
10437
|
+
/\bis?Pending\b/i,
|
|
10438
|
+
/\bis?Loading\b/i,
|
|
10439
|
+
/\bis?Submitting\b/i,
|
|
10440
|
+
/\bSpinner\b/
|
|
10441
|
+
],
|
|
10442
|
+
message: "Async action handler with no pending state. Without disabling/feedback the user can double-submit and gets no progress signal.",
|
|
10443
|
+
fix: "Track a pending flag and disable the control (and show a spinner) while the action runs."
|
|
10444
|
+
},
|
|
10445
|
+
{
|
|
10446
|
+
code: "ANAT-P008",
|
|
10447
|
+
id: "list-without-key",
|
|
10448
|
+
triggers: [/\.map\s*\(\s*\(?[^)]*\)?\s*=>\s*</],
|
|
10449
|
+
mitigations: [/\bkey=/],
|
|
10450
|
+
message: "Elements rendered from `.map(...)` with no `key` prop. Missing keys break reconciliation (lost state, mis-ordered DOM).",
|
|
10451
|
+
fix: "Add a stable `key` to the top-level mapped element: `items.map((i) => <Row key={i.id} />)`."
|
|
10452
|
+
},
|
|
10453
|
+
{
|
|
10454
|
+
code: "ANAT-P009",
|
|
10455
|
+
id: "router-without-not-found",
|
|
10456
|
+
triggers: [/<Routes\b/, /<Switch\b/, /\bcreateBrowserRouter\b/],
|
|
10457
|
+
mitigations: [
|
|
10458
|
+
/path=["']\*["']/,
|
|
10459
|
+
/path=["']\/\*["']/,
|
|
10460
|
+
/\bNotFound\b/,
|
|
10461
|
+
/\bcatchAll\b/i,
|
|
10462
|
+
/\b404\b/
|
|
10463
|
+
],
|
|
10464
|
+
message: "Route table with no catch-all / not-found route. Unmatched URLs render a blank page.",
|
|
10465
|
+
fix: 'Add a catch-all route (`<Route path="*" element={<NotFound/>} />`).'
|
|
10466
|
+
},
|
|
10467
|
+
{
|
|
10468
|
+
code: "ANAT-P010",
|
|
10469
|
+
id: "destructive-action-without-confirm",
|
|
10470
|
+
triggers: [
|
|
10471
|
+
/\b(onDelete|handleDelete|handleRemove|onRemove)\b/,
|
|
10472
|
+
/\b(delete|remove|destroy)\w*\s*\(/i
|
|
10473
|
+
],
|
|
10474
|
+
mitigations: [
|
|
10475
|
+
/\bconfirm\s*\(/i,
|
|
10476
|
+
/\bAlertDialog\b/,
|
|
10477
|
+
/\bConfirm(ation)?(Dialog|Modal)?\b/,
|
|
10478
|
+
/\bareYouSure\b/i,
|
|
10479
|
+
/\bwindow\.confirm\b/
|
|
10480
|
+
],
|
|
10481
|
+
message: "Destructive action (delete/remove) with no confirmation step. A single click irreversibly destroys data.",
|
|
10482
|
+
fix: "Gate the action behind a confirmation (e.g. an AlertDialog) before performing it."
|
|
10483
|
+
}
|
|
10484
|
+
];
|
|
10485
|
+
var PATTERN_CHECKS = SPECS.map(presencePattern);
|
|
10486
|
+
|
|
10010
10487
|
// src/mcp/tools/audit-anatomy.ts
|
|
10011
10488
|
var auditAnatomyDefinition = {
|
|
10012
10489
|
name: "audit_anatomy",
|
|
10013
|
-
description: "Audit components for anatomy completeness. Emits ANAT-D* findings for component definitions missing required slots/states (e.g., Button missing `content`). In v1 vertical slice runs the
|
|
10490
|
+
description: "Audit components for anatomy completeness. Emits ANAT-D* findings for component definitions missing required slots/states (e.g., Button missing `content`). In v1 vertical slice runs the component conventions, plus 10 ANAT-P* composition patterns (missing empty/loading/error states, modal dismiss, submit feedback, list keys, route 404, destructive-action confirm, \u2026) in full mode.",
|
|
10014
10491
|
inputSchema: {
|
|
10015
10492
|
type: "object",
|
|
10016
10493
|
properties: {
|
|
@@ -10018,7 +10495,7 @@ var auditAnatomyDefinition = {
|
|
|
10018
10495
|
mode: {
|
|
10019
10496
|
type: "string",
|
|
10020
10497
|
enum: ["fast", "full"],
|
|
10021
|
-
description: "fast = conventions only (cheap AST scan)
|
|
10498
|
+
description: "fast = conventions only (cheap AST scan); full additionally runs the ANAT-P* composition patterns."
|
|
10022
10499
|
},
|
|
10023
10500
|
files: {
|
|
10024
10501
|
type: "array",
|
|
@@ -10046,31 +10523,38 @@ async function runAudit(input) {
|
|
|
10046
10523
|
const projectRoot = sanitizePath(input.path);
|
|
10047
10524
|
const candidateFiles = input.files ?? [];
|
|
10048
10525
|
const conventionsApplied = /* @__PURE__ */ new Set();
|
|
10526
|
+
const patternsApplied = /* @__PURE__ */ new Set();
|
|
10049
10527
|
let totalFiles = 0;
|
|
10050
10528
|
for (const candidatePath of candidateFiles) {
|
|
10051
|
-
const absolute =
|
|
10529
|
+
const absolute = path24.isAbsolute(candidatePath) ? candidatePath : path24.join(projectRoot, candidatePath);
|
|
10052
10530
|
let contents;
|
|
10053
10531
|
try {
|
|
10054
|
-
contents =
|
|
10532
|
+
contents = fs22.readFileSync(absolute, "utf8");
|
|
10055
10533
|
} catch {
|
|
10056
10534
|
continue;
|
|
10057
10535
|
}
|
|
10058
10536
|
totalFiles += 1;
|
|
10537
|
+
const fileRelative = path24.relative(projectRoot, absolute).replaceAll("\\", "/") || absolute;
|
|
10059
10538
|
const componentType = resolveComponentType(absolute, contents);
|
|
10539
|
+
if (mode2 === "full") {
|
|
10540
|
+
for (const pattern of PATTERN_CHECKS) {
|
|
10541
|
+
const patternFindings = pattern.detect(fileRelative, contents, componentType);
|
|
10542
|
+
if (patternFindings.length > 0) patternsApplied.add(pattern.id);
|
|
10543
|
+
findings.push(...patternFindings);
|
|
10544
|
+
}
|
|
10545
|
+
}
|
|
10060
10546
|
if (componentType === null) continue;
|
|
10061
10547
|
const rule = resolveAnatomyRules(absolute, contents, componentType);
|
|
10062
10548
|
if (rule === null) continue;
|
|
10063
10549
|
const parsed = parseComponentDefinitionFromSource(absolute, contents);
|
|
10064
10550
|
if (parsed === null) continue;
|
|
10065
10551
|
conventionsApplied.add(rule.componentType);
|
|
10066
|
-
const fileRelative = path22.relative(projectRoot, absolute).replaceAll("\\", "/") || absolute;
|
|
10067
10552
|
const runnerOptions = { filePath: fileRelative };
|
|
10068
10553
|
if (input.designStrictness !== void 0) {
|
|
10069
10554
|
runnerOptions.strictness = input.designStrictness;
|
|
10070
10555
|
}
|
|
10071
10556
|
const fileFindings = runConventionRule(rule, parsed, runnerOptions);
|
|
10072
10557
|
findings.push(...fileFindings);
|
|
10073
|
-
void mode2;
|
|
10074
10558
|
}
|
|
10075
10559
|
const summary = buildSummary(findings, totalFiles, Date.now() - start);
|
|
10076
10560
|
return {
|
|
@@ -10078,7 +10562,7 @@ async function runAudit(input) {
|
|
|
10078
10562
|
summary,
|
|
10079
10563
|
catalog: {
|
|
10080
10564
|
conventionsApplied: [...conventionsApplied].sort(),
|
|
10081
|
-
patternsApplied: []
|
|
10565
|
+
patternsApplied: [...patternsApplied].sort()
|
|
10082
10566
|
},
|
|
10083
10567
|
meta: { mode: mode2, deferredToA11y: 0 }
|
|
10084
10568
|
};
|
|
@@ -10110,10 +10594,11 @@ async function handleAuditAnatomy(input) {
|
|
|
10110
10594
|
// src/mcp/tools/design-craft.ts
|
|
10111
10595
|
init_dist2();
|
|
10112
10596
|
import * as crypto2 from "crypto";
|
|
10597
|
+
import { execSync as execSync3 } from "child_process";
|
|
10113
10598
|
|
|
10114
10599
|
// src/design-craft/phases/critique.ts
|
|
10115
|
-
import * as
|
|
10116
|
-
import * as
|
|
10600
|
+
import * as fs23 from "fs";
|
|
10601
|
+
import * as path25 from "path";
|
|
10117
10602
|
|
|
10118
10603
|
// src/shared/craft/findings/derived.ts
|
|
10119
10604
|
var TIER_BAND = {
|
|
@@ -10195,8 +10680,8 @@ function parseFindingResponse(raw) {
|
|
|
10195
10680
|
}
|
|
10196
10681
|
function readSource(target) {
|
|
10197
10682
|
if (typeof target.source === "string") return target.source;
|
|
10198
|
-
const resolved =
|
|
10199
|
-
return
|
|
10683
|
+
const resolved = path25.isAbsolute(target.file) ? target.file : path25.resolve(process.cwd(), target.file);
|
|
10684
|
+
return fs23.readFileSync(resolved, "utf8");
|
|
10200
10685
|
}
|
|
10201
10686
|
function formatPrompt(template, targetId, source) {
|
|
10202
10687
|
return template.replace(/\{target\}/g, targetId).replace(/\{source\}/g, source);
|
|
@@ -10258,10 +10743,37 @@ async function runCritique(args) {
|
|
|
10258
10743
|
}
|
|
10259
10744
|
return findings;
|
|
10260
10745
|
}
|
|
10746
|
+
function readImage(imagePath) {
|
|
10747
|
+
const resolved = path25.isAbsolute(imagePath) ? imagePath : path25.resolve(process.cwd(), imagePath);
|
|
10748
|
+
const imageBuffer = fs23.readFileSync(resolved);
|
|
10749
|
+
const ext = path25.extname(resolved).toLowerCase();
|
|
10750
|
+
const mediaType = ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : ext === ".webp" ? "image/webp" : "image/png";
|
|
10751
|
+
return { imageBuffer, mediaType };
|
|
10752
|
+
}
|
|
10753
|
+
async function runVisionCritique(args) {
|
|
10754
|
+
const findings = [];
|
|
10755
|
+
for (const target of args.targets) {
|
|
10756
|
+
const image = readImage(target.image);
|
|
10757
|
+
const targetId = target.component ?? target.file;
|
|
10758
|
+
for (const rubric of args.rubrics) {
|
|
10759
|
+
const prompt = formatPrompt(
|
|
10760
|
+
rubric.prompt,
|
|
10761
|
+
targetId,
|
|
10762
|
+
"(the rendered component is attached as an image \u2014 judge the visual result, not source code)"
|
|
10763
|
+
);
|
|
10764
|
+
const raw = await args.provider.callVision(prompt, image);
|
|
10765
|
+
const parsed = parseFindingResponse(raw);
|
|
10766
|
+
findings.push(
|
|
10767
|
+
parsed ? buildFinding(target, rubric, parsed) : buildSentinelFinding(target, rubric, raw)
|
|
10768
|
+
);
|
|
10769
|
+
}
|
|
10770
|
+
}
|
|
10771
|
+
return findings;
|
|
10772
|
+
}
|
|
10261
10773
|
|
|
10262
10774
|
// src/design-craft/phases/polish.ts
|
|
10263
|
-
import * as
|
|
10264
|
-
import * as
|
|
10775
|
+
import * as fs24 from "fs";
|
|
10776
|
+
import * as path26 from "path";
|
|
10265
10777
|
var TIER_VALUES2 = ["foundational", "polish", "aspirational"];
|
|
10266
10778
|
var IMPACT_VALUES2 = ["small", "medium", "large"];
|
|
10267
10779
|
var CONFIDENCE_VALUES2 = ["high", "medium", "low"];
|
|
@@ -10322,8 +10834,8 @@ function parsePolishResponse(raw) {
|
|
|
10322
10834
|
}
|
|
10323
10835
|
function readSource2(target) {
|
|
10324
10836
|
if (typeof target.source === "string") return target.source;
|
|
10325
|
-
const resolved =
|
|
10326
|
-
return
|
|
10837
|
+
const resolved = path26.isAbsolute(target.file) ? target.file : path26.resolve(process.cwd(), target.file);
|
|
10838
|
+
return fs24.readFileSync(resolved, "utf8");
|
|
10327
10839
|
}
|
|
10328
10840
|
function patternIsPlausible(source, pattern) {
|
|
10329
10841
|
if (pattern.applicableTo.length === 0) return true;
|
|
@@ -10406,8 +10918,8 @@ async function runPolish(args) {
|
|
|
10406
10918
|
}
|
|
10407
10919
|
|
|
10408
10920
|
// src/design-craft/phases/benchmark.ts
|
|
10409
|
-
import * as
|
|
10410
|
-
import * as
|
|
10921
|
+
import * as fs25 from "fs";
|
|
10922
|
+
import * as path27 from "path";
|
|
10411
10923
|
var CONFIDENCE_VALUES3 = ["high", "medium", "low"];
|
|
10412
10924
|
function isConfidence3(v) {
|
|
10413
10925
|
return typeof v === "string" && CONFIDENCE_VALUES3.includes(v);
|
|
@@ -10473,8 +10985,8 @@ function parseBenchmarkResponse(raw) {
|
|
|
10473
10985
|
}
|
|
10474
10986
|
function readSource3(target) {
|
|
10475
10987
|
if (typeof target.source === "string") return target.source;
|
|
10476
|
-
const resolved =
|
|
10477
|
-
return
|
|
10988
|
+
const resolved = path27.isAbsolute(target.file) ? target.file : path27.resolve(process.cwd(), target.file);
|
|
10989
|
+
return fs25.readFileSync(resolved, "utf8");
|
|
10478
10990
|
}
|
|
10479
10991
|
var CONFIDENCE_RANK = {
|
|
10480
10992
|
high: 3,
|
|
@@ -12575,17 +13087,17 @@ import {
|
|
|
12575
13087
|
} from "@harness-engineering/intelligence";
|
|
12576
13088
|
|
|
12577
13089
|
// src/shared/craft/llm/orchestrator-md.ts
|
|
12578
|
-
import * as
|
|
12579
|
-
import * as
|
|
13090
|
+
import * as fs26 from "fs";
|
|
13091
|
+
import * as path28 from "path";
|
|
12580
13092
|
import { parse as parse4 } from "yaml";
|
|
12581
13093
|
var FILENAME = "harness.orchestrator.md";
|
|
12582
13094
|
function findOrchestratorMd(startDir) {
|
|
12583
|
-
let cur =
|
|
12584
|
-
const root =
|
|
13095
|
+
let cur = path28.resolve(startDir);
|
|
13096
|
+
const root = path28.parse(cur).root;
|
|
12585
13097
|
while (cur !== root) {
|
|
12586
|
-
const candidate =
|
|
12587
|
-
if (
|
|
12588
|
-
cur =
|
|
13098
|
+
const candidate = path28.join(cur, FILENAME);
|
|
13099
|
+
if (fs26.existsSync(candidate)) return candidate;
|
|
13100
|
+
cur = path28.dirname(cur);
|
|
12589
13101
|
}
|
|
12590
13102
|
return null;
|
|
12591
13103
|
}
|
|
@@ -12594,7 +13106,7 @@ function readBackendsFromOrchestratorMd(startDir) {
|
|
|
12594
13106
|
if (file === null) return null;
|
|
12595
13107
|
let raw;
|
|
12596
13108
|
try {
|
|
12597
|
-
raw =
|
|
13109
|
+
raw = fs26.readFileSync(file, "utf-8");
|
|
12598
13110
|
} catch {
|
|
12599
13111
|
return null;
|
|
12600
13112
|
}
|
|
@@ -12865,15 +13377,15 @@ function stringField(obj, key) {
|
|
|
12865
13377
|
}
|
|
12866
13378
|
|
|
12867
13379
|
// src/design-craft/measurement/usage.ts
|
|
12868
|
-
import * as
|
|
12869
|
-
import * as
|
|
12870
|
-
var DEFAULT_RELATIVE_PATH =
|
|
13380
|
+
import * as fs27 from "fs";
|
|
13381
|
+
import * as path29 from "path";
|
|
13382
|
+
var DEFAULT_RELATIVE_PATH = path29.join(".harness", "design-craft", "usage.json");
|
|
12871
13383
|
function emptyCounters() {
|
|
12872
13384
|
return { rubrics: {}, patterns: {}, exemplars: {} };
|
|
12873
13385
|
}
|
|
12874
13386
|
function resolveStorePath(projectRoot) {
|
|
12875
13387
|
const root = projectRoot ?? process.cwd();
|
|
12876
|
-
return
|
|
13388
|
+
return path29.resolve(root, DEFAULT_RELATIVE_PATH);
|
|
12877
13389
|
}
|
|
12878
13390
|
function isCounters(value) {
|
|
12879
13391
|
if (value === null || typeof value !== "object") return false;
|
|
@@ -12882,7 +13394,7 @@ function isCounters(value) {
|
|
|
12882
13394
|
}
|
|
12883
13395
|
function readCountersFromDisk(filePath) {
|
|
12884
13396
|
try {
|
|
12885
|
-
const raw =
|
|
13397
|
+
const raw = fs27.readFileSync(filePath, "utf8");
|
|
12886
13398
|
const parsed = JSON.parse(raw);
|
|
12887
13399
|
if (!isCounters(parsed)) return emptyCounters();
|
|
12888
13400
|
return {
|
|
@@ -12895,8 +13407,8 @@ function readCountersFromDisk(filePath) {
|
|
|
12895
13407
|
}
|
|
12896
13408
|
}
|
|
12897
13409
|
function writeCountersToDisk(filePath, counters) {
|
|
12898
|
-
|
|
12899
|
-
|
|
13410
|
+
fs27.mkdirSync(path29.dirname(filePath), { recursive: true });
|
|
13411
|
+
fs27.writeFileSync(filePath, JSON.stringify(counters, null, 2), "utf8");
|
|
12900
13412
|
}
|
|
12901
13413
|
function bumpCounter(family, id, projectRoot, count = 1) {
|
|
12902
13414
|
if (id.length === 0 || count <= 0) return;
|
|
@@ -12917,13 +13429,13 @@ function recordCite(exemplarId, projectRoot) {
|
|
|
12917
13429
|
}
|
|
12918
13430
|
|
|
12919
13431
|
// src/design-craft/measurement/signal.ts
|
|
12920
|
-
import * as
|
|
12921
|
-
import * as
|
|
13432
|
+
import * as fs28 from "fs";
|
|
13433
|
+
import * as path30 from "path";
|
|
12922
13434
|
import * as crypto from "crypto";
|
|
12923
|
-
var EVENTS_RELATIVE_PATH =
|
|
12924
|
-
var PROPOSALS_RELATIVE_DIR =
|
|
13435
|
+
var EVENTS_RELATIVE_PATH = path30.join(".harness", "design-craft", "signal-events.jsonl");
|
|
13436
|
+
var PROPOSALS_RELATIVE_DIR = path30.join(".harness", "design-craft", "proposals");
|
|
12925
13437
|
function resolveEventsPath(storeRoot) {
|
|
12926
|
-
return
|
|
13438
|
+
return path30.resolve(storeRoot ?? process.cwd(), EVENTS_RELATIVE_PATH);
|
|
12927
13439
|
}
|
|
12928
13440
|
function fingerprintFinding(finding) {
|
|
12929
13441
|
const raw = `${finding.code}|${finding.tier}|${finding.cite.rubricOrPatternId}`;
|
|
@@ -12944,8 +13456,8 @@ function recordSignalEvent(finding, projectRoot, storeRoot) {
|
|
|
12944
13456
|
};
|
|
12945
13457
|
try {
|
|
12946
13458
|
const file = resolveEventsPath(storeRoot);
|
|
12947
|
-
|
|
12948
|
-
|
|
13459
|
+
fs28.mkdirSync(path30.dirname(file), { recursive: true });
|
|
13460
|
+
fs28.appendFileSync(file, JSON.stringify(event) + "\n", "utf8");
|
|
12949
13461
|
} catch {
|
|
12950
13462
|
}
|
|
12951
13463
|
}
|
|
@@ -12962,7 +13474,7 @@ var designCraftToolDefinition = {
|
|
|
12962
13474
|
mode: {
|
|
12963
13475
|
type: "string",
|
|
12964
13476
|
enum: ["fast", "deep"],
|
|
12965
|
-
description: "fast (code-only LLM critique) or deep (
|
|
13477
|
+
description: "fast (code-only LLM critique) or deep (vision critique of rendered screenshots \u2014 requires `captures`)."
|
|
12966
13478
|
},
|
|
12967
13479
|
phases: {
|
|
12968
13480
|
type: "array",
|
|
@@ -12977,7 +13489,11 @@ var designCraftToolDefinition = {
|
|
|
12977
13489
|
autoCapture: {
|
|
12978
13490
|
type: "string",
|
|
12979
13491
|
enum: ["prompt", "auto", "skip"],
|
|
12980
|
-
description:
|
|
13492
|
+
description: 'Deep-mode capture behavior when no `captures` are supplied. "skip" never runs the capture command; "prompt"/"auto" run `captureCommand` when one is configured.'
|
|
13493
|
+
},
|
|
13494
|
+
captureCommand: {
|
|
13495
|
+
type: "string",
|
|
13496
|
+
description: "Deep-mode render+screenshot command. Receives the candidate files via the HARNESS_DESIGN_CRAFT_FILES env var (JSON array) and must print a JSON array of { file, image, component? } to stdout. Used to obtain captures without a built-in browser."
|
|
12981
13497
|
},
|
|
12982
13498
|
designStrictness: {
|
|
12983
13499
|
type: "string",
|
|
@@ -12996,6 +13512,19 @@ var designCraftToolDefinition = {
|
|
|
12996
13512
|
},
|
|
12997
13513
|
required: ["file", "component"]
|
|
12998
13514
|
}
|
|
13515
|
+
},
|
|
13516
|
+
captures: {
|
|
13517
|
+
type: "array",
|
|
13518
|
+
description: 'Deep-mode (vision) captures: rendered component screenshots. Required when mode="deep" and the critique phase runs. Each entry: { file, image, component? }, where `image` is a path to a PNG/JPEG/WebP screenshot (the CLI does not render components itself).',
|
|
13519
|
+
items: {
|
|
13520
|
+
type: "object",
|
|
13521
|
+
properties: {
|
|
13522
|
+
file: { type: "string" },
|
|
13523
|
+
image: { type: "string" },
|
|
13524
|
+
component: { type: "string" }
|
|
13525
|
+
},
|
|
13526
|
+
required: ["file", "image"]
|
|
13527
|
+
}
|
|
12999
13528
|
}
|
|
13000
13529
|
},
|
|
13001
13530
|
required: ["path"]
|
|
@@ -13036,16 +13565,63 @@ function summarizeLlmCalls(provider) {
|
|
|
13036
13565
|
async function runDesignCraft(input) {
|
|
13037
13566
|
return runPipeline(input);
|
|
13038
13567
|
}
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13568
|
+
function runCaptureCommand(command, files, exec) {
|
|
13569
|
+
let stdout;
|
|
13570
|
+
try {
|
|
13571
|
+
stdout = exec ? exec(command, files) : execSync3(command, {
|
|
13572
|
+
encoding: "utf-8",
|
|
13573
|
+
env: { ...process.env, HARNESS_DESIGN_CRAFT_FILES: JSON.stringify(files) },
|
|
13574
|
+
maxBuffer: 16 * 1024 * 1024
|
|
13575
|
+
});
|
|
13576
|
+
} catch (err2) {
|
|
13577
|
+
return Err({
|
|
13578
|
+
message: `design-craft capture command failed: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
13579
|
+
});
|
|
13580
|
+
}
|
|
13581
|
+
let parsed;
|
|
13582
|
+
try {
|
|
13583
|
+
parsed = JSON.parse(stdout);
|
|
13584
|
+
} catch {
|
|
13585
|
+
return Err({
|
|
13586
|
+
message: 'design-craft capture command did not emit valid JSON. Expected `[{ "file": "...", "image": "..." }]` on stdout.'
|
|
13587
|
+
});
|
|
13588
|
+
}
|
|
13589
|
+
if (!Array.isArray(parsed)) {
|
|
13590
|
+
return Err({
|
|
13591
|
+
message: "design-craft capture command output must be a JSON array of { file, image }."
|
|
13592
|
+
});
|
|
13593
|
+
}
|
|
13594
|
+
const captures = [];
|
|
13595
|
+
for (const item of parsed) {
|
|
13596
|
+
if (item !== null && typeof item === "object" && typeof item.file === "string" && typeof item.image === "string") {
|
|
13597
|
+
const entry = item;
|
|
13598
|
+
const capture = { file: entry.file, image: entry.image };
|
|
13599
|
+
if (typeof entry.component === "string") capture.component = entry.component;
|
|
13600
|
+
captures.push(capture);
|
|
13601
|
+
}
|
|
13602
|
+
}
|
|
13603
|
+
if (captures.length === 0) {
|
|
13042
13604
|
return Err({
|
|
13043
|
-
message:
|
|
13605
|
+
message: "design-craft capture command produced no valid { file, image } entries."
|
|
13044
13606
|
});
|
|
13045
13607
|
}
|
|
13608
|
+
return Ok(captures);
|
|
13609
|
+
}
|
|
13610
|
+
async function runPipeline(input) {
|
|
13611
|
+
const mode2 = input.mode ?? "fast";
|
|
13046
13612
|
const phases = selectPhases(input.phases);
|
|
13047
13613
|
const autoCapture = input.autoCapture ?? "prompt";
|
|
13048
|
-
|
|
13614
|
+
let captures = input.captures ?? [];
|
|
13615
|
+
const needsCaptures = mode2 === "deep" && phases.includes("critique") && captures.length === 0;
|
|
13616
|
+
if (needsCaptures && input.captureCommand && autoCapture !== "skip") {
|
|
13617
|
+
const captured = runCaptureCommand(input.captureCommand, input.files ?? [], input.__runCapture);
|
|
13618
|
+
if (!captured.ok) return captured;
|
|
13619
|
+
captures = captured.value;
|
|
13620
|
+
}
|
|
13621
|
+
if (mode2 === "deep" && phases.includes("critique") && captures.length === 0) {
|
|
13622
|
+
return Err({
|
|
13623
|
+
message: 'design-craft deep mode critiques rendered screenshots \u2014 supply `captures` ([{ file, image }]) or configure `captureCommand` to render them (the CLI does not render components itself). Or use mode: "fast".'
|
|
13624
|
+
});
|
|
13049
13625
|
}
|
|
13050
13626
|
const provider = input.__testProvider ?? getProvider();
|
|
13051
13627
|
const critiqueTargets = buildTargetsFromFiles(input.files);
|
|
@@ -13057,12 +13633,14 @@ async function runPipeline(input) {
|
|
|
13057
13633
|
const findings = [];
|
|
13058
13634
|
const scores = [];
|
|
13059
13635
|
let rubricsApplied = [];
|
|
13060
|
-
if (phases.includes("critique")
|
|
13636
|
+
if (phases.includes("critique")) {
|
|
13061
13637
|
const rubrics = [...SEED_RUBRICS];
|
|
13062
|
-
|
|
13063
|
-
|
|
13638
|
+
const critiqueFindings = mode2 === "deep" ? await runVisionCritique({ targets: captures, rubrics, provider }) : critiqueTargets.length > 0 ? await runCritique({ targets: critiqueTargets, rubrics, provider }) : [];
|
|
13639
|
+
if (critiqueFindings.length > 0 || mode2 === "deep") {
|
|
13640
|
+
rubricsApplied = rubrics.map((r) => r.id);
|
|
13641
|
+
}
|
|
13064
13642
|
findings.push(...critiqueFindings);
|
|
13065
|
-
if (recordMeasurement) {
|
|
13643
|
+
if (recordMeasurement && critiqueFindings.length > 0) {
|
|
13066
13644
|
for (const rubric of rubrics) recordTrigger(rubric.id, measurementRoot);
|
|
13067
13645
|
for (const f of critiqueFindings) recordSignalEvent(f, measurementRoot, measurementRoot);
|
|
13068
13646
|
}
|
|
@@ -13135,18 +13713,18 @@ async function handleDesignCraft(input) {
|
|
|
13135
13713
|
}
|
|
13136
13714
|
|
|
13137
13715
|
// src/drift/index.ts
|
|
13138
|
-
import * as
|
|
13139
|
-
import * as
|
|
13716
|
+
import * as fs31 from "fs";
|
|
13717
|
+
import * as path33 from "path";
|
|
13140
13718
|
|
|
13141
13719
|
// src/drift/resolvers/tokens.ts
|
|
13142
|
-
import * as
|
|
13143
|
-
import * as
|
|
13720
|
+
import * as fs29 from "fs";
|
|
13721
|
+
import * as path31 from "path";
|
|
13144
13722
|
function loadTokenSet(projectRoot) {
|
|
13145
|
-
const tokenPath =
|
|
13146
|
-
if (!
|
|
13723
|
+
const tokenPath = path31.join(projectRoot, "design-system", "tokens.json");
|
|
13724
|
+
if (!fs29.existsSync(tokenPath)) return null;
|
|
13147
13725
|
let raw;
|
|
13148
13726
|
try {
|
|
13149
|
-
raw =
|
|
13727
|
+
raw = fs29.readFileSync(tokenPath, "utf-8");
|
|
13150
13728
|
} catch {
|
|
13151
13729
|
return null;
|
|
13152
13730
|
}
|
|
@@ -13170,11 +13748,11 @@ function extractTokens(root) {
|
|
|
13170
13748
|
return set;
|
|
13171
13749
|
}
|
|
13172
13750
|
function loadTokenPathIndex(projectRoot) {
|
|
13173
|
-
const tokenPath =
|
|
13174
|
-
if (!
|
|
13751
|
+
const tokenPath = path31.join(projectRoot, "design-system", "tokens.json");
|
|
13752
|
+
if (!fs29.existsSync(tokenPath)) return null;
|
|
13175
13753
|
let raw;
|
|
13176
13754
|
try {
|
|
13177
|
-
raw =
|
|
13755
|
+
raw = fs29.readFileSync(tokenPath, "utf-8");
|
|
13178
13756
|
} catch {
|
|
13179
13757
|
return null;
|
|
13180
13758
|
}
|
|
@@ -13198,23 +13776,7 @@ function extractPathIndex(root) {
|
|
|
13198
13776
|
}
|
|
13199
13777
|
function walkForPaths(node, breadcrumb, idx) {
|
|
13200
13778
|
if ("$value" in node) {
|
|
13201
|
-
|
|
13202
|
-
const $type = typeof node.$type === "string" ? node.$type : void 0;
|
|
13203
|
-
const $value = node.$value;
|
|
13204
|
-
if ($type === "color" && typeof $value === "string") {
|
|
13205
|
-
pushPath(idx.colorPath, $value.toLowerCase(), tokenPath);
|
|
13206
|
-
} else if ($type === "fontFamily" && typeof $value === "string") {
|
|
13207
|
-
pushPath(idx.fontFamilyPath, $value.toLowerCase(), tokenPath);
|
|
13208
|
-
} else if ($type === "fontFamily" && Array.isArray($value)) {
|
|
13209
|
-
for (const f of $value) {
|
|
13210
|
-
if (typeof f === "string") pushPath(idx.fontFamilyPath, f.toLowerCase(), tokenPath);
|
|
13211
|
-
}
|
|
13212
|
-
} else if (($type === "dimension" || $type === "spacing") && typeof $value === "string") {
|
|
13213
|
-
const px = parsePxValue($value);
|
|
13214
|
-
if (px !== null) pushPath(idx.spacingPath, px, tokenPath);
|
|
13215
|
-
} else if (($type === "dimension" || $type === "spacing") && typeof $value === "number") {
|
|
13216
|
-
pushPath(idx.spacingPath, $value, tokenPath);
|
|
13217
|
-
}
|
|
13779
|
+
collectPathInto(node, breadcrumb, idx);
|
|
13218
13780
|
return;
|
|
13219
13781
|
}
|
|
13220
13782
|
for (const [key, value] of Object.entries(node)) {
|
|
@@ -13224,33 +13786,23 @@ function walkForPaths(node, breadcrumb, idx) {
|
|
|
13224
13786
|
}
|
|
13225
13787
|
}
|
|
13226
13788
|
}
|
|
13227
|
-
function
|
|
13789
|
+
function collectPathInto(node, breadcrumb, idx) {
|
|
13790
|
+
const tokenPath = breadcrumb.join(".");
|
|
13791
|
+
const $type = typeof node.$type === "string" ? node.$type : void 0;
|
|
13792
|
+
const $value = node.$value;
|
|
13793
|
+
const parts = classifyTokenValue($type, $value);
|
|
13794
|
+
for (const c of parts.colors) pushPath(idx.colorPath, c, tokenPath);
|
|
13795
|
+
for (const f of parts.fontFamilies) pushPath(idx.fontFamilyPath, f, tokenPath);
|
|
13796
|
+
for (const px of parts.spacingPx) pushPath(idx.spacingPath, px, tokenPath);
|
|
13797
|
+
}
|
|
13798
|
+
function pushPath(map, key, path56) {
|
|
13228
13799
|
const list = map.get(key) ?? [];
|
|
13229
|
-
list.push(
|
|
13800
|
+
list.push(path56);
|
|
13230
13801
|
map.set(key, list);
|
|
13231
13802
|
}
|
|
13232
13803
|
function walk(node, breadcrumb, set) {
|
|
13233
13804
|
if ("$value" in node) {
|
|
13234
|
-
|
|
13235
|
-
const $type = typeof node.$type === "string" ? node.$type : void 0;
|
|
13236
|
-
const $value = node.$value;
|
|
13237
|
-
if (node.$deprecated === true || isHarnessDeprecated(node.$extensions)) {
|
|
13238
|
-
set.deprecatedTokens.add(tokenPath);
|
|
13239
|
-
}
|
|
13240
|
-
if ($type === "color" && typeof $value === "string") {
|
|
13241
|
-
set.colors.add($value.toLowerCase());
|
|
13242
|
-
} else if ($type === "fontFamily" && typeof $value === "string") {
|
|
13243
|
-
set.fontFamilies.add($value.toLowerCase());
|
|
13244
|
-
} else if ($type === "fontFamily" && Array.isArray($value)) {
|
|
13245
|
-
for (const f of $value) {
|
|
13246
|
-
if (typeof f === "string") set.fontFamilies.add(f.toLowerCase());
|
|
13247
|
-
}
|
|
13248
|
-
} else if (($type === "dimension" || $type === "spacing") && typeof $value === "string") {
|
|
13249
|
-
const px = parsePxValue($value);
|
|
13250
|
-
if (px !== null) set.spacingPx.add(px);
|
|
13251
|
-
} else if (($type === "dimension" || $type === "spacing") && typeof $value === "number") {
|
|
13252
|
-
set.spacingPx.add($value);
|
|
13253
|
-
}
|
|
13805
|
+
collectTokenInto(node, breadcrumb, set);
|
|
13254
13806
|
return;
|
|
13255
13807
|
}
|
|
13256
13808
|
for (const [key, value] of Object.entries(node)) {
|
|
@@ -13260,6 +13812,46 @@ function walk(node, breadcrumb, set) {
|
|
|
13260
13812
|
}
|
|
13261
13813
|
}
|
|
13262
13814
|
}
|
|
13815
|
+
function collectTokenInto(node, breadcrumb, set) {
|
|
13816
|
+
const tokenPath = breadcrumb.join(".");
|
|
13817
|
+
const $type = typeof node.$type === "string" ? node.$type : void 0;
|
|
13818
|
+
const $value = node.$value;
|
|
13819
|
+
if (node.$deprecated === true || isHarnessDeprecated(node.$extensions)) {
|
|
13820
|
+
set.deprecatedTokens.add(tokenPath);
|
|
13821
|
+
}
|
|
13822
|
+
const parts = classifyTokenValue($type, $value);
|
|
13823
|
+
for (const c of parts.colors) set.colors.add(c);
|
|
13824
|
+
for (const f of parts.fontFamilies) set.fontFamilies.add(f);
|
|
13825
|
+
for (const px of parts.spacingPx) set.spacingPx.add(px);
|
|
13826
|
+
}
|
|
13827
|
+
function classifyTokenValue($type, $value) {
|
|
13828
|
+
return {
|
|
13829
|
+
colors: colorValues($type, $value),
|
|
13830
|
+
fontFamilies: fontFamilyValues($type, $value),
|
|
13831
|
+
spacingPx: spacingValues($type, $value)
|
|
13832
|
+
};
|
|
13833
|
+
}
|
|
13834
|
+
function colorValues($type, $value) {
|
|
13835
|
+
if ($type === "color" && typeof $value === "string") return [$value.toLowerCase()];
|
|
13836
|
+
return [];
|
|
13837
|
+
}
|
|
13838
|
+
function fontFamilyValues($type, $value) {
|
|
13839
|
+
if ($type !== "fontFamily") return [];
|
|
13840
|
+
if (typeof $value === "string") return [$value.toLowerCase()];
|
|
13841
|
+
if (Array.isArray($value)) {
|
|
13842
|
+
return $value.filter((f) => typeof f === "string").map((f) => f.toLowerCase());
|
|
13843
|
+
}
|
|
13844
|
+
return [];
|
|
13845
|
+
}
|
|
13846
|
+
function spacingValues($type, $value) {
|
|
13847
|
+
if ($type !== "dimension" && $type !== "spacing") return [];
|
|
13848
|
+
if (typeof $value === "string") {
|
|
13849
|
+
const px = parsePxValue($value);
|
|
13850
|
+
return px !== null ? [px] : [];
|
|
13851
|
+
}
|
|
13852
|
+
if (typeof $value === "number") return [$value];
|
|
13853
|
+
return [];
|
|
13854
|
+
}
|
|
13263
13855
|
function isHarnessDeprecated(extensions) {
|
|
13264
13856
|
if (!extensions || typeof extensions !== "object") return false;
|
|
13265
13857
|
const harness = extensions["harness"];
|
|
@@ -13273,8 +13865,8 @@ function parsePxValue(s) {
|
|
|
13273
13865
|
}
|
|
13274
13866
|
|
|
13275
13867
|
// src/drift/resolvers/component-registry.ts
|
|
13276
|
-
import * as
|
|
13277
|
-
import * as
|
|
13868
|
+
import * as fs30 from "fs";
|
|
13869
|
+
import * as path32 from "path";
|
|
13278
13870
|
var HTML_PRIMITIVE_MAP = {
|
|
13279
13871
|
Button: "button",
|
|
13280
13872
|
Input: "input",
|
|
@@ -13283,11 +13875,11 @@ var HTML_PRIMITIVE_MAP = {
|
|
|
13283
13875
|
Anchor: "a"
|
|
13284
13876
|
};
|
|
13285
13877
|
function loadComponentRegistry(projectRoot) {
|
|
13286
|
-
const designMdPath =
|
|
13287
|
-
if (!
|
|
13878
|
+
const designMdPath = path32.join(projectRoot, "design-system", "DESIGN.md");
|
|
13879
|
+
if (!fs30.existsSync(designMdPath)) return null;
|
|
13288
13880
|
let raw;
|
|
13289
13881
|
try {
|
|
13290
|
-
raw =
|
|
13882
|
+
raw = fs30.readFileSync(designMdPath, "utf-8");
|
|
13291
13883
|
} catch {
|
|
13292
13884
|
return null;
|
|
13293
13885
|
}
|
|
@@ -13607,40 +14199,76 @@ function extractLine2(source, offset) {
|
|
|
13607
14199
|
|
|
13608
14200
|
// src/drift/index.ts
|
|
13609
14201
|
var DEFAULT_GLOB_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".css", ".scss"];
|
|
13610
|
-
|
|
13611
|
-
const startedAt = Date.now();
|
|
14202
|
+
function resolveDriftConfig(input) {
|
|
13612
14203
|
const projectRoot = sanitizePath(input.path);
|
|
13613
|
-
const mode2 = input.mode ?? "fast";
|
|
13614
|
-
const strictness = input.designStrictness ?? "standard";
|
|
13615
14204
|
const tokenBypassEnabled = input.rules?.tokenBypass !== false;
|
|
13616
14205
|
const primitiveAdoptionEnabled = input.rules?.primitiveAdoption !== false;
|
|
13617
|
-
|
|
13618
|
-
|
|
14206
|
+
return {
|
|
14207
|
+
projectRoot,
|
|
14208
|
+
mode: input.mode ?? "fast",
|
|
14209
|
+
strictness: input.designStrictness ?? "standard",
|
|
14210
|
+
tokenBypassEnabled,
|
|
14211
|
+
primitiveAdoptionEnabled,
|
|
14212
|
+
tokens: tokenBypassEnabled ? loadTokenSet(projectRoot) : null,
|
|
14213
|
+
registry: primitiveAdoptionEnabled ? loadComponentRegistry(projectRoot) : null
|
|
14214
|
+
};
|
|
14215
|
+
}
|
|
14216
|
+
function computeRulesApplied(config) {
|
|
13619
14217
|
const rulesApplied = [];
|
|
13620
|
-
if (tokenBypassEnabled && tokens !== null) rulesApplied.push("token-bypass");
|
|
13621
|
-
if (primitiveAdoptionEnabled && registry !== null)
|
|
13622
|
-
|
|
14218
|
+
if (config.tokenBypassEnabled && config.tokens !== null) rulesApplied.push("token-bypass");
|
|
14219
|
+
if (config.primitiveAdoptionEnabled && config.registry !== null) {
|
|
14220
|
+
rulesApplied.push("primitive-adoption");
|
|
14221
|
+
}
|
|
14222
|
+
return rulesApplied;
|
|
14223
|
+
}
|
|
14224
|
+
function scanFile(file, config) {
|
|
14225
|
+
let source;
|
|
14226
|
+
try {
|
|
14227
|
+
source = fs31.readFileSync(file, "utf-8");
|
|
14228
|
+
} catch {
|
|
14229
|
+
return [];
|
|
14230
|
+
}
|
|
13623
14231
|
const findings = [];
|
|
13624
|
-
|
|
13625
|
-
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
} catch {
|
|
13629
|
-
continue;
|
|
13630
|
-
}
|
|
13631
|
-
if (tokenBypassEnabled && tokens !== null) {
|
|
13632
|
-
findings.push(...runTokenBypassRule({ source, file, tokens, strictness }));
|
|
13633
|
-
}
|
|
13634
|
-
if (primitiveAdoptionEnabled && registry !== null) {
|
|
13635
|
-
findings.push(...runPrimitiveAdoptionRule({ source, file, registry, strictness }));
|
|
13636
|
-
}
|
|
14232
|
+
if (config.tokenBypassEnabled && config.tokens !== null) {
|
|
14233
|
+
findings.push(
|
|
14234
|
+
...runTokenBypassRule({ source, file, tokens: config.tokens, strictness: config.strictness })
|
|
14235
|
+
);
|
|
13637
14236
|
}
|
|
14237
|
+
if (config.primitiveAdoptionEnabled && config.registry !== null) {
|
|
14238
|
+
findings.push(
|
|
14239
|
+
...runPrimitiveAdoptionRule({
|
|
14240
|
+
source,
|
|
14241
|
+
file,
|
|
14242
|
+
registry: config.registry,
|
|
14243
|
+
strictness: config.strictness
|
|
14244
|
+
})
|
|
14245
|
+
);
|
|
14246
|
+
}
|
|
14247
|
+
return findings;
|
|
14248
|
+
}
|
|
14249
|
+
function scanFiles(files, config) {
|
|
14250
|
+
const findings = [];
|
|
14251
|
+
for (const file of files) {
|
|
14252
|
+
findings.push(...scanFile(file, config));
|
|
14253
|
+
}
|
|
14254
|
+
return findings;
|
|
14255
|
+
}
|
|
14256
|
+
function summarizeFindings(findings) {
|
|
13638
14257
|
const bySeverity = { error: 0, warn: 0, info: 0 };
|
|
13639
14258
|
const byCode2 = {};
|
|
13640
14259
|
for (const f of findings) {
|
|
13641
14260
|
bySeverity[f.severity] = (bySeverity[f.severity] ?? 0) + 1;
|
|
13642
14261
|
byCode2[f.code] = (byCode2[f.code] ?? 0) + 1;
|
|
13643
14262
|
}
|
|
14263
|
+
return { bySeverity, byCode: byCode2 };
|
|
14264
|
+
}
|
|
14265
|
+
async function runDetectDrift(input) {
|
|
14266
|
+
const startedAt = Date.now();
|
|
14267
|
+
const config = resolveDriftConfig(input);
|
|
14268
|
+
const rulesApplied = computeRulesApplied(config);
|
|
14269
|
+
const filesToScan = await collectFiles(config.projectRoot, input.files);
|
|
14270
|
+
const findings = scanFiles(filesToScan, config);
|
|
14271
|
+
const { bySeverity, byCode: byCode2 } = summarizeFindings(findings);
|
|
13644
14272
|
return {
|
|
13645
14273
|
findings,
|
|
13646
14274
|
summary: {
|
|
@@ -13651,15 +14279,15 @@ async function runDetectDrift(input) {
|
|
|
13651
14279
|
},
|
|
13652
14280
|
catalog: { rulesApplied },
|
|
13653
14281
|
meta: {
|
|
13654
|
-
mode:
|
|
13655
|
-
tokensLoaded: tokens !== null,
|
|
13656
|
-
registryLoaded: registry !== null
|
|
14282
|
+
mode: config.mode,
|
|
14283
|
+
tokensLoaded: config.tokens !== null,
|
|
14284
|
+
registryLoaded: config.registry !== null
|
|
13657
14285
|
}
|
|
13658
14286
|
};
|
|
13659
14287
|
}
|
|
13660
14288
|
async function collectFiles(projectRoot, explicitFiles) {
|
|
13661
14289
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
13662
|
-
return explicitFiles.map((f) =>
|
|
14290
|
+
return explicitFiles.map((f) => path33.isAbsolute(f) ? f : path33.join(projectRoot, f));
|
|
13663
14291
|
}
|
|
13664
14292
|
const out = [];
|
|
13665
14293
|
walk2(projectRoot, out, 0);
|
|
@@ -13669,7 +14297,7 @@ function walk2(dir, out, depth) {
|
|
|
13669
14297
|
if (depth > 8) return;
|
|
13670
14298
|
let entries;
|
|
13671
14299
|
try {
|
|
13672
|
-
entries =
|
|
14300
|
+
entries = fs31.readdirSync(dir, { withFileTypes: true });
|
|
13673
14301
|
} catch {
|
|
13674
14302
|
return;
|
|
13675
14303
|
}
|
|
@@ -13677,7 +14305,7 @@ function walk2(dir, out, depth) {
|
|
|
13677
14305
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
13678
14306
|
continue;
|
|
13679
14307
|
}
|
|
13680
|
-
const full =
|
|
14308
|
+
const full = path33.join(dir, entry.name);
|
|
13681
14309
|
if (entry.isDirectory()) {
|
|
13682
14310
|
walk2(full, out, depth + 1);
|
|
13683
14311
|
} else if (entry.isFile() && DEFAULT_GLOB_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -13745,8 +14373,8 @@ async function handleDetectDrift(input) {
|
|
|
13745
14373
|
}
|
|
13746
14374
|
|
|
13747
14375
|
// src/align/index.ts
|
|
13748
|
-
import * as
|
|
13749
|
-
import * as
|
|
14376
|
+
import * as fs33 from "fs";
|
|
14377
|
+
import * as path36 from "path";
|
|
13750
14378
|
|
|
13751
14379
|
// src/align/classifier/token-import.ts
|
|
13752
14380
|
var NAMED_IMPORT = /^\s*import\s*\{\s*tokens(?:\s+as\s+\w+)?\s*\}\s*from\s+['"][^'"]+['"];?\s*$/m;
|
|
@@ -13886,9 +14514,9 @@ function sourceLine(source, line) {
|
|
|
13886
14514
|
}
|
|
13887
14515
|
|
|
13888
14516
|
// src/align/codemods/common.ts
|
|
13889
|
-
import * as
|
|
14517
|
+
import * as path34 from "path";
|
|
13890
14518
|
function renderTokenReference(file, tokenPath) {
|
|
13891
|
-
const ext =
|
|
14519
|
+
const ext = path34.extname(file).toLowerCase();
|
|
13892
14520
|
if (ext === ".css" || ext === ".scss") {
|
|
13893
14521
|
return `var(--${tokenPath.replace(/\./g, "-")})`;
|
|
13894
14522
|
}
|
|
@@ -14040,8 +14668,8 @@ function emitPrimitiveSuggestion(finding) {
|
|
|
14040
14668
|
}
|
|
14041
14669
|
|
|
14042
14670
|
// src/align/revert/state.ts
|
|
14043
|
-
import * as
|
|
14044
|
-
import * as
|
|
14671
|
+
import * as fs32 from "fs";
|
|
14672
|
+
import * as path35 from "path";
|
|
14045
14673
|
import { createHash as createHash2 } from "crypto";
|
|
14046
14674
|
var LAST_BATCH_PATH = ".harness/align/last-batch.json";
|
|
14047
14675
|
function hashContent(content) {
|
|
@@ -14073,15 +14701,15 @@ function saveLastBatch(projectRoot, outcomes, mode2, fileReader) {
|
|
|
14073
14701
|
mode: mode2,
|
|
14074
14702
|
entries
|
|
14075
14703
|
};
|
|
14076
|
-
const full =
|
|
14077
|
-
|
|
14078
|
-
|
|
14704
|
+
const full = path35.join(projectRoot, LAST_BATCH_PATH);
|
|
14705
|
+
fs32.mkdirSync(path35.dirname(full), { recursive: true });
|
|
14706
|
+
fs32.writeFileSync(full, JSON.stringify(batch, null, 2) + "\n", "utf-8");
|
|
14079
14707
|
}
|
|
14080
14708
|
function loadLastBatch(projectRoot) {
|
|
14081
|
-
const full =
|
|
14082
|
-
if (!
|
|
14709
|
+
const full = path35.join(projectRoot, LAST_BATCH_PATH);
|
|
14710
|
+
if (!fs32.existsSync(full)) return null;
|
|
14083
14711
|
try {
|
|
14084
|
-
const raw =
|
|
14712
|
+
const raw = fs32.readFileSync(full, "utf-8");
|
|
14085
14713
|
const parsed = JSON.parse(raw);
|
|
14086
14714
|
if (parsed.version !== 1 || !Array.isArray(parsed.entries)) return null;
|
|
14087
14715
|
return parsed;
|
|
@@ -14178,7 +14806,7 @@ function persistRunArtifacts(args) {
|
|
|
14178
14806
|
saveLastBatch(args.projectRoot, args.outcomes, args.mode, (file) => {
|
|
14179
14807
|
const cached = args.sourceCache.get(file);
|
|
14180
14808
|
if (cached !== void 0) return cached;
|
|
14181
|
-
return
|
|
14809
|
+
return fs33.readFileSync(file, "utf-8");
|
|
14182
14810
|
});
|
|
14183
14811
|
}
|
|
14184
14812
|
async function runRevert(projectRoot, mode2, dryRun, startedAt) {
|
|
@@ -14244,7 +14872,7 @@ function readForRevert(file, sourceCache) {
|
|
|
14244
14872
|
const cached = sourceCache.get(file);
|
|
14245
14873
|
if (cached !== void 0) return { kind: "ok", text: cached };
|
|
14246
14874
|
try {
|
|
14247
|
-
return { kind: "ok", text:
|
|
14875
|
+
return { kind: "ok", text: fs33.readFileSync(file, "utf-8") };
|
|
14248
14876
|
} catch (err2) {
|
|
14249
14877
|
return {
|
|
14250
14878
|
kind: "failed",
|
|
@@ -14254,7 +14882,7 @@ function readForRevert(file, sourceCache) {
|
|
|
14254
14882
|
}
|
|
14255
14883
|
function tryWrite(file, content) {
|
|
14256
14884
|
try {
|
|
14257
|
-
|
|
14885
|
+
fs33.writeFileSync(file, content, "utf-8");
|
|
14258
14886
|
return null;
|
|
14259
14887
|
} catch (err2) {
|
|
14260
14888
|
return `cannot write source file: ${err2 instanceof Error ? err2.message : String(err2)}`;
|
|
@@ -14262,10 +14890,10 @@ function tryWrite(file, content) {
|
|
|
14262
14890
|
}
|
|
14263
14891
|
async function loadFindings(projectRoot, input, mode2) {
|
|
14264
14892
|
if (mode2 === "pipeline") {
|
|
14265
|
-
const handoffPath =
|
|
14266
|
-
if (!
|
|
14893
|
+
const handoffPath = path36.join(projectRoot, HANDOFF_PATH);
|
|
14894
|
+
if (!fs33.existsSync(handoffPath)) return [];
|
|
14267
14895
|
try {
|
|
14268
|
-
const raw =
|
|
14896
|
+
const raw = fs33.readFileSync(handoffPath, "utf-8");
|
|
14269
14897
|
const parsed = JSON.parse(raw);
|
|
14270
14898
|
return parsed.pipeline?.driftFindings ?? [];
|
|
14271
14899
|
} catch {
|
|
@@ -14290,7 +14918,7 @@ async function processFinding(finding, tokenPaths, sourceCache, dryRun) {
|
|
|
14290
14918
|
}
|
|
14291
14919
|
let source;
|
|
14292
14920
|
try {
|
|
14293
|
-
source = sourceCache.get(finding.file) ??
|
|
14921
|
+
source = sourceCache.get(finding.file) ?? fs33.readFileSync(finding.file, "utf-8");
|
|
14294
14922
|
sourceCache.set(finding.file, source);
|
|
14295
14923
|
} catch (err2) {
|
|
14296
14924
|
return {
|
|
@@ -14318,7 +14946,7 @@ async function processFinding(finding, tokenPaths, sourceCache, dryRun) {
|
|
|
14318
14946
|
}
|
|
14319
14947
|
if (!dryRun) {
|
|
14320
14948
|
try {
|
|
14321
|
-
|
|
14949
|
+
fs33.writeFileSync(finding.file, result.newSource, "utf-8");
|
|
14322
14950
|
sourceCache.set(finding.file, result.newSource);
|
|
14323
14951
|
} catch (err2) {
|
|
14324
14952
|
return {
|
|
@@ -14380,19 +15008,19 @@ function aggregateOutput(outcomes, meta) {
|
|
|
14380
15008
|
};
|
|
14381
15009
|
}
|
|
14382
15010
|
function writePipelineFixesApplied(projectRoot, outcomes) {
|
|
14383
|
-
const handoffPath =
|
|
15011
|
+
const handoffPath = path36.join(projectRoot, HANDOFF_PATH);
|
|
14384
15012
|
let handoff = {};
|
|
14385
|
-
if (
|
|
15013
|
+
if (fs33.existsSync(handoffPath)) {
|
|
14386
15014
|
try {
|
|
14387
|
-
handoff = JSON.parse(
|
|
15015
|
+
handoff = JSON.parse(fs33.readFileSync(handoffPath, "utf-8"));
|
|
14388
15016
|
} catch {
|
|
14389
15017
|
handoff = {};
|
|
14390
15018
|
}
|
|
14391
15019
|
}
|
|
14392
15020
|
handoff.pipeline = handoff.pipeline ?? {};
|
|
14393
15021
|
handoff.pipeline.fixesApplied = outcomes;
|
|
14394
|
-
|
|
14395
|
-
|
|
15022
|
+
fs33.mkdirSync(path36.dirname(handoffPath), { recursive: true });
|
|
15023
|
+
fs33.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n", "utf-8");
|
|
14396
15024
|
}
|
|
14397
15025
|
function findingKey(finding) {
|
|
14398
15026
|
return `${finding.code}@${finding.file}:${finding.line ?? "?"}`;
|
|
@@ -14468,18 +15096,18 @@ async function handleAlignDesignSystem(input) {
|
|
|
14468
15096
|
}
|
|
14469
15097
|
|
|
14470
15098
|
// src/brand/index.ts
|
|
14471
|
-
import * as
|
|
14472
|
-
import * as
|
|
15099
|
+
import * as fs36 from "fs";
|
|
15100
|
+
import * as path39 from "path";
|
|
14473
15101
|
|
|
14474
15102
|
// src/brand/resolvers/design-md-brand.ts
|
|
14475
|
-
import * as
|
|
14476
|
-
import * as
|
|
15103
|
+
import * as fs34 from "fs";
|
|
15104
|
+
import * as path37 from "path";
|
|
14477
15105
|
function loadBrandRules(projectRoot) {
|
|
14478
|
-
const designMdPath =
|
|
14479
|
-
if (!
|
|
15106
|
+
const designMdPath = path37.join(projectRoot, "design-system", "DESIGN.md");
|
|
15107
|
+
if (!fs34.existsSync(designMdPath)) return null;
|
|
14480
15108
|
let raw;
|
|
14481
15109
|
try {
|
|
14482
|
-
raw =
|
|
15110
|
+
raw = fs34.readFileSync(designMdPath, "utf-8");
|
|
14483
15111
|
} catch {
|
|
14484
15112
|
return null;
|
|
14485
15113
|
}
|
|
@@ -14590,14 +15218,14 @@ function parseAssets(block) {
|
|
|
14590
15218
|
}
|
|
14591
15219
|
|
|
14592
15220
|
// src/brand/resolvers/token-extensions.ts
|
|
14593
|
-
import * as
|
|
14594
|
-
import * as
|
|
15221
|
+
import * as fs35 from "fs";
|
|
15222
|
+
import * as path38 from "path";
|
|
14595
15223
|
function loadBrandTokenIndex(projectRoot) {
|
|
14596
|
-
const tokenPath =
|
|
14597
|
-
if (!
|
|
15224
|
+
const tokenPath = path38.join(projectRoot, "design-system", "tokens.json");
|
|
15225
|
+
if (!fs35.existsSync(tokenPath)) return null;
|
|
14598
15226
|
let raw;
|
|
14599
15227
|
try {
|
|
14600
|
-
raw =
|
|
15228
|
+
raw = fs35.readFileSync(tokenPath, "utf-8");
|
|
14601
15229
|
} catch {
|
|
14602
15230
|
return null;
|
|
14603
15231
|
}
|
|
@@ -14827,56 +15455,39 @@ function checkText(text, position, sourceFile, file, phrasesLc, strictness, seen
|
|
|
14827
15455
|
|
|
14828
15456
|
// src/brand/index.ts
|
|
14829
15457
|
var DEFAULT_GLOB_EXTENSIONS2 = [".ts", ".tsx", ".js", ".jsx", ".css", ".scss"];
|
|
15458
|
+
function resolveOptions(input) {
|
|
15459
|
+
return {
|
|
15460
|
+
mode: input.mode ?? "fast",
|
|
15461
|
+
strictness: input.designStrictness ?? "standard",
|
|
15462
|
+
tokenMisuseEnabled: input.rules?.tokenMisuse !== false,
|
|
15463
|
+
voiceEnabled: input.rules?.voice !== false
|
|
15464
|
+
};
|
|
15465
|
+
}
|
|
14830
15466
|
async function runAuditBrand(input) {
|
|
14831
15467
|
const startedAt = Date.now();
|
|
14832
15468
|
const projectRoot = sanitizePath(input.path);
|
|
14833
|
-
const mode2 = input
|
|
14834
|
-
const strictness = input.designStrictness ?? "standard";
|
|
14835
|
-
const tokenMisuseEnabled = input.rules?.tokenMisuse !== false;
|
|
14836
|
-
const voiceEnabled = input.rules?.voice !== false;
|
|
15469
|
+
const { mode: mode2, strictness, tokenMisuseEnabled, voiceEnabled } = resolveOptions(input);
|
|
14837
15470
|
const brandRules = voiceEnabled ? loadBrandRules(projectRoot) : null;
|
|
14838
15471
|
const brandTokens = tokenMisuseEnabled ? loadBrandTokenIndex(projectRoot) : null;
|
|
15472
|
+
const tokenActive = tokenMisuseEnabled && brandTokens !== null;
|
|
15473
|
+
const voiceActive = isVoiceRuleActive(voiceEnabled, brandRules);
|
|
14839
15474
|
const rulesApplied = [];
|
|
14840
|
-
if (
|
|
14841
|
-
if (
|
|
14842
|
-
rulesApplied.push("forbidden-phrases");
|
|
14843
|
-
}
|
|
15475
|
+
if (tokenActive) rulesApplied.push("token-misuse");
|
|
15476
|
+
if (voiceActive) rulesApplied.push("forbidden-phrases");
|
|
14844
15477
|
const filesToScan = collectFiles2(projectRoot, input.files);
|
|
14845
|
-
const findings =
|
|
14846
|
-
|
|
14847
|
-
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
}
|
|
14853
|
-
if (tokenMisuseEnabled && brandTokens !== null) {
|
|
14854
|
-
findings.push(...runTokenMisuseRule({ source, file, brandTokens, strictness }));
|
|
14855
|
-
}
|
|
14856
|
-
if (voiceEnabled && brandRules?.voice && brandRules.voice.forbiddenPhrases.length > 0) {
|
|
14857
|
-
findings.push(
|
|
14858
|
-
...runForbiddenPhrasesRule({
|
|
14859
|
-
source,
|
|
14860
|
-
file,
|
|
14861
|
-
forbiddenPhrases: brandRules.voice.forbiddenPhrases,
|
|
14862
|
-
strictness
|
|
14863
|
-
})
|
|
14864
|
-
);
|
|
14865
|
-
}
|
|
14866
|
-
}
|
|
14867
|
-
const bySeverity = { error: 0, warn: 0, info: 0 };
|
|
14868
|
-
const byCode2 = {};
|
|
14869
|
-
for (const f of findings) {
|
|
14870
|
-
bySeverity[f.severity] = (bySeverity[f.severity] ?? 0) + 1;
|
|
14871
|
-
byCode2[f.code] = (byCode2[f.code] ?? 0) + 1;
|
|
14872
|
-
}
|
|
15478
|
+
const findings = scanFiles2(filesToScan, {
|
|
15479
|
+
tokenActive,
|
|
15480
|
+
voiceActive,
|
|
15481
|
+
brandTokens,
|
|
15482
|
+
brandRules,
|
|
15483
|
+
strictness
|
|
15484
|
+
});
|
|
14873
15485
|
return {
|
|
14874
15486
|
findings,
|
|
14875
15487
|
summary: {
|
|
14876
15488
|
totalFiles: filesToScan.length,
|
|
14877
15489
|
durationMs: Date.now() - startedAt,
|
|
14878
|
-
|
|
14879
|
-
byCode: byCode2
|
|
15490
|
+
...tallyFindings(findings)
|
|
14880
15491
|
},
|
|
14881
15492
|
catalog: { rulesApplied },
|
|
14882
15493
|
meta: {
|
|
@@ -14886,9 +15497,58 @@ async function runAuditBrand(input) {
|
|
|
14886
15497
|
}
|
|
14887
15498
|
};
|
|
14888
15499
|
}
|
|
15500
|
+
function isVoiceRuleActive(voiceEnabled, brandRules) {
|
|
15501
|
+
return voiceEnabled && brandRules?.voice != null && brandRules.voice.forbiddenPhrases.length > 0;
|
|
15502
|
+
}
|
|
15503
|
+
function scanFiles2(files, ctx) {
|
|
15504
|
+
const findings = [];
|
|
15505
|
+
for (const file of files) {
|
|
15506
|
+
let source;
|
|
15507
|
+
try {
|
|
15508
|
+
source = fs36.readFileSync(file, "utf-8");
|
|
15509
|
+
} catch {
|
|
15510
|
+
continue;
|
|
15511
|
+
}
|
|
15512
|
+
findings.push(...scanSource(source, file, ctx));
|
|
15513
|
+
}
|
|
15514
|
+
return findings;
|
|
15515
|
+
}
|
|
15516
|
+
function scanSource(source, file, ctx) {
|
|
15517
|
+
const findings = [];
|
|
15518
|
+
if (ctx.tokenActive && ctx.brandTokens !== null) {
|
|
15519
|
+
findings.push(
|
|
15520
|
+
...runTokenMisuseRule({
|
|
15521
|
+
source,
|
|
15522
|
+
file,
|
|
15523
|
+
brandTokens: ctx.brandTokens,
|
|
15524
|
+
strictness: ctx.strictness
|
|
15525
|
+
})
|
|
15526
|
+
);
|
|
15527
|
+
}
|
|
15528
|
+
if (ctx.voiceActive && ctx.brandRules?.voice) {
|
|
15529
|
+
findings.push(
|
|
15530
|
+
...runForbiddenPhrasesRule({
|
|
15531
|
+
source,
|
|
15532
|
+
file,
|
|
15533
|
+
forbiddenPhrases: ctx.brandRules.voice.forbiddenPhrases,
|
|
15534
|
+
strictness: ctx.strictness
|
|
15535
|
+
})
|
|
15536
|
+
);
|
|
15537
|
+
}
|
|
15538
|
+
return findings;
|
|
15539
|
+
}
|
|
15540
|
+
function tallyFindings(findings) {
|
|
15541
|
+
const bySeverity = { error: 0, warn: 0, info: 0 };
|
|
15542
|
+
const byCode2 = {};
|
|
15543
|
+
for (const f of findings) {
|
|
15544
|
+
bySeverity[f.severity] = (bySeverity[f.severity] ?? 0) + 1;
|
|
15545
|
+
byCode2[f.code] = (byCode2[f.code] ?? 0) + 1;
|
|
15546
|
+
}
|
|
15547
|
+
return { bySeverity, byCode: byCode2 };
|
|
15548
|
+
}
|
|
14889
15549
|
function collectFiles2(projectRoot, explicitFiles) {
|
|
14890
15550
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
14891
|
-
return explicitFiles.map((f) =>
|
|
15551
|
+
return explicitFiles.map((f) => path39.isAbsolute(f) ? f : path39.join(projectRoot, f));
|
|
14892
15552
|
}
|
|
14893
15553
|
const out = [];
|
|
14894
15554
|
walk4(projectRoot, out, 0);
|
|
@@ -14898,7 +15558,7 @@ function walk4(dir, out, depth) {
|
|
|
14898
15558
|
if (depth > 8) return;
|
|
14899
15559
|
let entries;
|
|
14900
15560
|
try {
|
|
14901
|
-
entries =
|
|
15561
|
+
entries = fs36.readdirSync(dir, { withFileTypes: true });
|
|
14902
15562
|
} catch {
|
|
14903
15563
|
return;
|
|
14904
15564
|
}
|
|
@@ -14906,7 +15566,7 @@ function walk4(dir, out, depth) {
|
|
|
14906
15566
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
14907
15567
|
continue;
|
|
14908
15568
|
}
|
|
14909
|
-
const full =
|
|
15569
|
+
const full = path39.join(dir, entry.name);
|
|
14910
15570
|
if (entry.isDirectory()) {
|
|
14911
15571
|
walk4(full, out, depth + 1);
|
|
14912
15572
|
} else if (entry.isFile() && DEFAULT_GLOB_EXTENSIONS2.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -15024,15 +15684,15 @@ var VerifierRegistry = class {
|
|
|
15024
15684
|
};
|
|
15025
15685
|
|
|
15026
15686
|
// src/design-pipeline/phases/freshen.ts
|
|
15027
|
-
import * as
|
|
15028
|
-
import * as
|
|
15687
|
+
import * as fs37 from "fs";
|
|
15688
|
+
import * as path40 from "path";
|
|
15029
15689
|
function runFreshen(input) {
|
|
15030
15690
|
const { projectRoot, context } = input;
|
|
15031
|
-
context.graphAvailable =
|
|
15032
|
-
const designMdPath =
|
|
15033
|
-
const tokensJsonPath =
|
|
15034
|
-
context.inputs.designMdExists =
|
|
15035
|
-
context.inputs.tokensJsonExists =
|
|
15691
|
+
context.graphAvailable = fs37.existsSync(path40.join(projectRoot, ".harness", "graph"));
|
|
15692
|
+
const designMdPath = path40.join(projectRoot, "design-system", "DESIGN.md");
|
|
15693
|
+
const tokensJsonPath = path40.join(projectRoot, "design-system", "tokens.json");
|
|
15694
|
+
context.inputs.designMdExists = fs37.existsSync(designMdPath);
|
|
15695
|
+
context.inputs.tokensJsonExists = fs37.existsSync(tokensJsonPath);
|
|
15036
15696
|
if (context.inputs.designMdExists) {
|
|
15037
15697
|
const content = readFileSafe(designMdPath);
|
|
15038
15698
|
context.inputs.componentRegistryExists = content !== null && /^##\s+component\s+registry\b/im.test(content);
|
|
@@ -15041,7 +15701,7 @@ function runFreshen(input) {
|
|
|
15041
15701
|
}
|
|
15042
15702
|
function readFileSafe(p) {
|
|
15043
15703
|
try {
|
|
15044
|
-
return
|
|
15704
|
+
return fs37.readFileSync(p, "utf-8");
|
|
15045
15705
|
} catch {
|
|
15046
15706
|
return null;
|
|
15047
15707
|
}
|
|
@@ -15068,8 +15728,8 @@ async function runDetect(input) {
|
|
|
15068
15728
|
}
|
|
15069
15729
|
|
|
15070
15730
|
// src/design-pipeline/phases/fix.ts
|
|
15071
|
-
import * as
|
|
15072
|
-
import * as
|
|
15731
|
+
import * as fs38 from "fs";
|
|
15732
|
+
import * as path41 from "path";
|
|
15073
15733
|
var MAX_ITERATIONS = 5;
|
|
15074
15734
|
async function runFix(input) {
|
|
15075
15735
|
const { projectRoot, context, mode: mode2, files, designStrictness } = input;
|
|
@@ -15118,11 +15778,11 @@ async function safelyRedetect(projectRoot, mode2, files, designStrictness) {
|
|
|
15118
15778
|
}
|
|
15119
15779
|
}
|
|
15120
15780
|
function writePipelineHandoff(projectRoot, context) {
|
|
15121
|
-
const handoffPath =
|
|
15781
|
+
const handoffPath = path41.join(projectRoot, ".harness", "handoff.json");
|
|
15122
15782
|
let handoff = {};
|
|
15123
|
-
if (
|
|
15783
|
+
if (fs38.existsSync(handoffPath)) {
|
|
15124
15784
|
try {
|
|
15125
|
-
handoff = JSON.parse(
|
|
15785
|
+
handoff = JSON.parse(fs38.readFileSync(handoffPath, "utf-8"));
|
|
15126
15786
|
} catch {
|
|
15127
15787
|
handoff = {};
|
|
15128
15788
|
}
|
|
@@ -15130,8 +15790,8 @@ function writePipelineHandoff(projectRoot, context) {
|
|
|
15130
15790
|
handoff.pipeline = {
|
|
15131
15791
|
driftFindings: context.driftFindings
|
|
15132
15792
|
};
|
|
15133
|
-
|
|
15134
|
-
|
|
15793
|
+
fs38.mkdirSync(path41.dirname(handoffPath), { recursive: true });
|
|
15794
|
+
fs38.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n");
|
|
15135
15795
|
}
|
|
15136
15796
|
|
|
15137
15797
|
// src/design-pipeline/phases/audit.ts
|
|
@@ -15161,8 +15821,8 @@ async function runAudit2(input) {
|
|
|
15161
15821
|
}
|
|
15162
15822
|
|
|
15163
15823
|
// src/design-pipeline/phases/fill.ts
|
|
15164
|
-
import * as
|
|
15165
|
-
import * as
|
|
15824
|
+
import * as fs39 from "fs";
|
|
15825
|
+
import * as path42 from "path";
|
|
15166
15826
|
var DESIGN_MD_STUB = `# Design System
|
|
15167
15827
|
|
|
15168
15828
|
<!-- TODO: author this file. See harness-design skill for guidance. -->
|
|
@@ -15222,11 +15882,11 @@ async function runFill(input) {
|
|
|
15222
15882
|
}
|
|
15223
15883
|
function bootstrapMissingInputs(input) {
|
|
15224
15884
|
const { projectRoot, context } = input;
|
|
15225
|
-
const designMdPath =
|
|
15226
|
-
const tokensJsonPath =
|
|
15227
|
-
if (!
|
|
15228
|
-
|
|
15229
|
-
|
|
15885
|
+
const designMdPath = path42.join(projectRoot, "design-system", "DESIGN.md");
|
|
15886
|
+
const tokensJsonPath = path42.join(projectRoot, "design-system", "tokens.json");
|
|
15887
|
+
if (!fs39.existsSync(designMdPath)) {
|
|
15888
|
+
fs39.mkdirSync(path42.dirname(designMdPath), { recursive: true });
|
|
15889
|
+
fs39.writeFileSync(designMdPath, DESIGN_MD_STUB);
|
|
15230
15890
|
context.bootstrapped.designMd = true;
|
|
15231
15891
|
context.inputs.designMdExists = true;
|
|
15232
15892
|
context.inputs.componentRegistryExists = true;
|
|
@@ -15246,16 +15906,16 @@ function bootstrapMissingInputs(input) {
|
|
|
15246
15906
|
}
|
|
15247
15907
|
}
|
|
15248
15908
|
}
|
|
15249
|
-
if (!
|
|
15250
|
-
|
|
15251
|
-
|
|
15909
|
+
if (!fs39.existsSync(tokensJsonPath)) {
|
|
15910
|
+
fs39.mkdirSync(path42.dirname(tokensJsonPath), { recursive: true });
|
|
15911
|
+
fs39.writeFileSync(tokensJsonPath, TOKENS_JSON_STUB);
|
|
15252
15912
|
context.bootstrapped.tokensJson = true;
|
|
15253
15913
|
context.inputs.tokensJsonExists = true;
|
|
15254
15914
|
}
|
|
15255
15915
|
}
|
|
15256
15916
|
function readFileSafe2(p) {
|
|
15257
15917
|
try {
|
|
15258
|
-
return
|
|
15918
|
+
return fs39.readFileSync(p, "utf-8");
|
|
15259
15919
|
} catch {
|
|
15260
15920
|
return null;
|
|
15261
15921
|
}
|
|
@@ -15290,10 +15950,10 @@ async function invokeCraftPolish(input) {
|
|
|
15290
15950
|
function appendToFile(p, content) {
|
|
15291
15951
|
let existing = "";
|
|
15292
15952
|
try {
|
|
15293
|
-
existing =
|
|
15953
|
+
existing = fs39.readFileSync(p, "utf-8");
|
|
15294
15954
|
} catch {
|
|
15295
15955
|
}
|
|
15296
|
-
|
|
15956
|
+
fs39.writeFileSync(p, existing + content);
|
|
15297
15957
|
}
|
|
15298
15958
|
|
|
15299
15959
|
// src/design-pipeline/phases/report.ts
|
|
@@ -15442,8 +16102,8 @@ async function handleDesignPipeline(input) {
|
|
|
15442
16102
|
}
|
|
15443
16103
|
|
|
15444
16104
|
// src/naming-craft/index.ts
|
|
15445
|
-
import * as
|
|
15446
|
-
import * as
|
|
16105
|
+
import * as fs41 from "fs";
|
|
16106
|
+
import * as path44 from "path";
|
|
15447
16107
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
15448
16108
|
|
|
15449
16109
|
// src/naming-craft/extract/identifiers.ts
|
|
@@ -15459,47 +16119,61 @@ function extractIdentifiers(file, source) {
|
|
|
15459
16119
|
const out = [];
|
|
15460
16120
|
const sourceLines = source.split("\n");
|
|
15461
16121
|
function visit5(node, parentBodyLineSize) {
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
node.name.text,
|
|
15465
|
-
"function",
|
|
15466
|
-
node,
|
|
15467
|
-
sourceFile,
|
|
15468
|
-
sourceLines,
|
|
15469
|
-
out,
|
|
15470
|
-
parentBodyLineSize
|
|
15471
|
-
);
|
|
15472
|
-
}
|
|
15473
|
-
if ((ts4.isInterfaceDeclaration(node) || ts4.isTypeAliasDeclaration(node) || ts4.isClassDeclaration(node)) && node.name !== void 0) {
|
|
15474
|
-
pushIdentifier(
|
|
15475
|
-
node.name.text,
|
|
15476
|
-
"type",
|
|
15477
|
-
node,
|
|
15478
|
-
sourceFile,
|
|
15479
|
-
sourceLines,
|
|
15480
|
-
out,
|
|
15481
|
-
parentBodyLineSize
|
|
15482
|
-
);
|
|
15483
|
-
}
|
|
15484
|
-
if (ts4.isVariableStatement(node)) {
|
|
15485
|
-
for (const decl of node.declarationList.declarations) {
|
|
15486
|
-
for (const binding of collectBindingNames(decl.name)) {
|
|
15487
|
-
const kind = decl.initializer !== void 0 && (ts4.isArrowFunction(decl.initializer) || ts4.isFunctionExpression(decl.initializer)) ? "function" : "variable";
|
|
15488
|
-
pushIdentifier(binding, kind, decl, sourceFile, sourceLines, out, parentBodyLineSize);
|
|
15489
|
-
}
|
|
15490
|
-
}
|
|
15491
|
-
}
|
|
15492
|
-
let nextParentBodyLineSize = parentBodyLineSize;
|
|
15493
|
-
if ((ts4.isFunctionDeclaration(node) || ts4.isFunctionExpression(node) || ts4.isArrowFunction(node) || ts4.isMethodDeclaration(node)) && node.body !== void 0) {
|
|
15494
|
-
const start = sourceFile.getLineAndCharacterOfPosition(node.body.getStart(sourceFile)).line;
|
|
15495
|
-
const end = sourceFile.getLineAndCharacterOfPosition(node.body.getEnd()).line;
|
|
15496
|
-
nextParentBodyLineSize = end - start + 1;
|
|
15497
|
-
}
|
|
16122
|
+
collectDeclaredIdentifiers(node, sourceFile, sourceLines, out, parentBodyLineSize);
|
|
16123
|
+
const nextParentBodyLineSize = computeChildBodyLineSize(node, sourceFile, parentBodyLineSize);
|
|
15498
16124
|
ts4.forEachChild(node, (child) => visit5(child, nextParentBodyLineSize));
|
|
15499
16125
|
}
|
|
15500
16126
|
visit5(sourceFile, null);
|
|
15501
16127
|
return out;
|
|
15502
16128
|
}
|
|
16129
|
+
function collectDeclaredIdentifiers(node, sourceFile, sourceLines, out, parentBodyLineSize) {
|
|
16130
|
+
if (ts4.isFunctionDeclaration(node) && node.name !== void 0) {
|
|
16131
|
+
pushIdentifier(
|
|
16132
|
+
node.name.text,
|
|
16133
|
+
"function",
|
|
16134
|
+
node,
|
|
16135
|
+
sourceFile,
|
|
16136
|
+
sourceLines,
|
|
16137
|
+
out,
|
|
16138
|
+
parentBodyLineSize
|
|
16139
|
+
);
|
|
16140
|
+
return;
|
|
16141
|
+
}
|
|
16142
|
+
if (isTypeLikeDeclaration(node) && node.name !== void 0) {
|
|
16143
|
+
pushIdentifier(node.name.text, "type", node, sourceFile, sourceLines, out, parentBodyLineSize);
|
|
16144
|
+
return;
|
|
16145
|
+
}
|
|
16146
|
+
if (ts4.isVariableStatement(node)) {
|
|
16147
|
+
pushVariableIdentifiers(node, sourceFile, sourceLines, out, parentBodyLineSize);
|
|
16148
|
+
}
|
|
16149
|
+
}
|
|
16150
|
+
function isTypeLikeDeclaration(node) {
|
|
16151
|
+
return ts4.isInterfaceDeclaration(node) || ts4.isTypeAliasDeclaration(node) || ts4.isClassDeclaration(node);
|
|
16152
|
+
}
|
|
16153
|
+
function pushVariableIdentifiers(node, sourceFile, sourceLines, out, parentBodyLineSize) {
|
|
16154
|
+
for (const decl of node.declarationList.declarations) {
|
|
16155
|
+
for (const binding of collectBindingNames(decl.name)) {
|
|
16156
|
+
const kind = variableInitializerKind(decl.initializer);
|
|
16157
|
+
pushIdentifier(binding, kind, decl, sourceFile, sourceLines, out, parentBodyLineSize);
|
|
16158
|
+
}
|
|
16159
|
+
}
|
|
16160
|
+
}
|
|
16161
|
+
function variableInitializerKind(initializer) {
|
|
16162
|
+
return initializer !== void 0 && (ts4.isArrowFunction(initializer) || ts4.isFunctionExpression(initializer)) ? "function" : "variable";
|
|
16163
|
+
}
|
|
16164
|
+
function computeChildBodyLineSize(node, sourceFile, parentBodyLineSize) {
|
|
16165
|
+
const body = functionBodyOf(node);
|
|
16166
|
+
if (body === void 0) return parentBodyLineSize;
|
|
16167
|
+
const start = sourceFile.getLineAndCharacterOfPosition(body.getStart(sourceFile)).line;
|
|
16168
|
+
const end = sourceFile.getLineAndCharacterOfPosition(body.getEnd()).line;
|
|
16169
|
+
return end - start + 1;
|
|
16170
|
+
}
|
|
16171
|
+
function functionBodyOf(node) {
|
|
16172
|
+
if ((ts4.isFunctionDeclaration(node) || ts4.isFunctionExpression(node) || ts4.isArrowFunction(node) || ts4.isMethodDeclaration(node)) && node.body !== void 0) {
|
|
16173
|
+
return node.body;
|
|
16174
|
+
}
|
|
16175
|
+
return void 0;
|
|
16176
|
+
}
|
|
15503
16177
|
function pushIdentifier(name, kind, node, sourceFile, sourceLines, out, parentBodyLineSize) {
|
|
15504
16178
|
const { line: zeroLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
15505
16179
|
const line = zeroLine + 1;
|
|
@@ -15767,27 +16441,27 @@ function isConfidence4(v) {
|
|
|
15767
16441
|
}
|
|
15768
16442
|
|
|
15769
16443
|
// src/shared/craft/runs/store.ts
|
|
15770
|
-
import * as
|
|
15771
|
-
import * as
|
|
16444
|
+
import * as fs40 from "fs";
|
|
16445
|
+
import * as path43 from "path";
|
|
15772
16446
|
var RUN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
15773
16447
|
function runsDir(projectRoot) {
|
|
15774
|
-
return
|
|
16448
|
+
return path43.join(projectRoot, ".harness", "craft", "runs");
|
|
15775
16449
|
}
|
|
15776
16450
|
function runPath(projectRoot, runId) {
|
|
15777
|
-
return
|
|
16451
|
+
return path43.join(runsDir(projectRoot), `${runId}.json`);
|
|
15778
16452
|
}
|
|
15779
16453
|
function saveRunState(projectRoot, state) {
|
|
15780
16454
|
const dir = runsDir(projectRoot);
|
|
15781
|
-
|
|
16455
|
+
fs40.mkdirSync(dir, { recursive: true });
|
|
15782
16456
|
const file = runPath(projectRoot, state.runId);
|
|
15783
|
-
|
|
16457
|
+
fs40.writeFileSync(file, JSON.stringify(state, null, 2));
|
|
15784
16458
|
return { runFile: file };
|
|
15785
16459
|
}
|
|
15786
16460
|
function loadRunState(projectRoot, runId) {
|
|
15787
16461
|
const file = runPath(projectRoot, runId);
|
|
15788
|
-
if (!
|
|
16462
|
+
if (!fs40.existsSync(file)) return null;
|
|
15789
16463
|
try {
|
|
15790
|
-
const raw =
|
|
16464
|
+
const raw = fs40.readFileSync(file, "utf-8");
|
|
15791
16465
|
return JSON.parse(raw);
|
|
15792
16466
|
} catch {
|
|
15793
16467
|
return null;
|
|
@@ -15796,7 +16470,7 @@ function loadRunState(projectRoot, runId) {
|
|
|
15796
16470
|
function deleteRunState(projectRoot, runId) {
|
|
15797
16471
|
const file = runPath(projectRoot, runId);
|
|
15798
16472
|
try {
|
|
15799
|
-
|
|
16473
|
+
fs40.unlinkSync(file);
|
|
15800
16474
|
} catch {
|
|
15801
16475
|
}
|
|
15802
16476
|
}
|
|
@@ -15804,16 +16478,16 @@ function pruneOldRuns(projectRoot, now = Date.now()) {
|
|
|
15804
16478
|
const dir = runsDir(projectRoot);
|
|
15805
16479
|
let entries;
|
|
15806
16480
|
try {
|
|
15807
|
-
entries =
|
|
16481
|
+
entries = fs40.readdirSync(dir, { withFileTypes: true });
|
|
15808
16482
|
} catch {
|
|
15809
16483
|
return;
|
|
15810
16484
|
}
|
|
15811
16485
|
for (const entry of entries) {
|
|
15812
16486
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
15813
|
-
const full =
|
|
16487
|
+
const full = path43.join(dir, entry.name);
|
|
15814
16488
|
try {
|
|
15815
|
-
const stat =
|
|
15816
|
-
if (now - stat.mtimeMs > RUN_TTL_MS)
|
|
16489
|
+
const stat = fs40.statSync(full);
|
|
16490
|
+
if (now - stat.mtimeMs > RUN_TTL_MS) fs40.unlinkSync(full);
|
|
15817
16491
|
} catch {
|
|
15818
16492
|
}
|
|
15819
16493
|
}
|
|
@@ -15833,7 +16507,7 @@ function gatherProjectContext(input) {
|
|
|
15833
16507
|
for (const file of files) {
|
|
15834
16508
|
let source;
|
|
15835
16509
|
try {
|
|
15836
|
-
source =
|
|
16510
|
+
source = fs41.readFileSync(file, "utf-8");
|
|
15837
16511
|
} catch {
|
|
15838
16512
|
continue;
|
|
15839
16513
|
}
|
|
@@ -15990,7 +16664,7 @@ async function finalizeNamingCraft(input) {
|
|
|
15990
16664
|
}
|
|
15991
16665
|
function collectFiles3(projectRoot, explicitFiles) {
|
|
15992
16666
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
15993
|
-
return explicitFiles.map((f) =>
|
|
16667
|
+
return explicitFiles.map((f) => path44.isAbsolute(f) ? f : path44.join(projectRoot, f));
|
|
15994
16668
|
}
|
|
15995
16669
|
const out = [];
|
|
15996
16670
|
walk5(projectRoot, out, 0);
|
|
@@ -16000,7 +16674,7 @@ function walk5(dir, out, depth) {
|
|
|
16000
16674
|
if (depth > 8) return;
|
|
16001
16675
|
let entries;
|
|
16002
16676
|
try {
|
|
16003
|
-
entries =
|
|
16677
|
+
entries = fs41.readdirSync(dir, { withFileTypes: true });
|
|
16004
16678
|
} catch {
|
|
16005
16679
|
return;
|
|
16006
16680
|
}
|
|
@@ -16008,7 +16682,7 @@ function walk5(dir, out, depth) {
|
|
|
16008
16682
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
16009
16683
|
continue;
|
|
16010
16684
|
}
|
|
16011
|
-
const full =
|
|
16685
|
+
const full = path44.join(dir, entry.name);
|
|
16012
16686
|
if (entry.isDirectory()) walk5(full, out, depth + 1);
|
|
16013
16687
|
else if (entry.isFile() && EXTENSIONS.some((ext) => entry.name.endsWith(ext))) out.push(full);
|
|
16014
16688
|
}
|
|
@@ -16192,32 +16866,32 @@ async function handleNamingCraftFinalize(input) {
|
|
|
16192
16866
|
}
|
|
16193
16867
|
|
|
16194
16868
|
// src/spec-craft/index.ts
|
|
16195
|
-
import * as
|
|
16869
|
+
import * as fs43 from "fs";
|
|
16196
16870
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
16197
16871
|
|
|
16198
16872
|
// src/spec-craft/extract/discover.ts
|
|
16199
|
-
import * as
|
|
16200
|
-
import * as
|
|
16873
|
+
import * as fs42 from "fs";
|
|
16874
|
+
import * as path45 from "path";
|
|
16201
16875
|
function discoverSpecs(projectRoot, kindsFilter) {
|
|
16202
16876
|
const wantProposal = kindsFilter === void 0 || kindsFilter.includes("proposal");
|
|
16203
16877
|
const wantADR = kindsFilter === void 0 || kindsFilter.includes("adr");
|
|
16204
16878
|
const out = [];
|
|
16205
16879
|
if (wantProposal) {
|
|
16206
|
-
const changesDir =
|
|
16207
|
-
if (
|
|
16208
|
-
for (const entry of
|
|
16880
|
+
const changesDir = path45.join(projectRoot, "docs", "changes");
|
|
16881
|
+
if (fs42.existsSync(changesDir)) {
|
|
16882
|
+
for (const entry of fs42.readdirSync(changesDir, { withFileTypes: true })) {
|
|
16209
16883
|
if (!entry.isDirectory()) continue;
|
|
16210
16884
|
if (entry.name.startsWith(".")) continue;
|
|
16211
|
-
const topicDir =
|
|
16212
|
-
const direct =
|
|
16213
|
-
if (
|
|
16885
|
+
const topicDir = path45.join(changesDir, entry.name);
|
|
16886
|
+
const direct = path45.join(topicDir, "proposal.md");
|
|
16887
|
+
if (fs42.existsSync(direct)) {
|
|
16214
16888
|
out.push({ file: direct, kind: "proposal" });
|
|
16215
16889
|
}
|
|
16216
|
-
for (const sub of
|
|
16890
|
+
for (const sub of fs42.readdirSync(topicDir, { withFileTypes: true })) {
|
|
16217
16891
|
if (!sub.isDirectory()) continue;
|
|
16218
16892
|
if (sub.name.startsWith(".")) continue;
|
|
16219
|
-
const subProposal =
|
|
16220
|
-
if (
|
|
16893
|
+
const subProposal = path45.join(topicDir, sub.name, "proposal.md");
|
|
16894
|
+
if (fs42.existsSync(subProposal)) {
|
|
16221
16895
|
out.push({ file: subProposal, kind: "proposal" });
|
|
16222
16896
|
}
|
|
16223
16897
|
}
|
|
@@ -16225,13 +16899,13 @@ function discoverSpecs(projectRoot, kindsFilter) {
|
|
|
16225
16899
|
}
|
|
16226
16900
|
}
|
|
16227
16901
|
if (wantADR) {
|
|
16228
|
-
const decisionsDir =
|
|
16229
|
-
if (
|
|
16230
|
-
for (const entry of
|
|
16902
|
+
const decisionsDir = path45.join(projectRoot, "docs", "knowledge", "decisions");
|
|
16903
|
+
if (fs42.existsSync(decisionsDir)) {
|
|
16904
|
+
for (const entry of fs42.readdirSync(decisionsDir, { withFileTypes: true })) {
|
|
16231
16905
|
if (!entry.isFile()) continue;
|
|
16232
16906
|
if (!entry.name.endsWith(".md")) continue;
|
|
16233
16907
|
if (entry.name.startsWith(".") || entry.name.toUpperCase() === "README.MD") continue;
|
|
16234
|
-
out.push({ file:
|
|
16908
|
+
out.push({ file: path45.join(decisionsDir, entry.name), kind: "adr" });
|
|
16235
16909
|
}
|
|
16236
16910
|
}
|
|
16237
16911
|
}
|
|
@@ -16479,7 +17153,7 @@ async function runSpecCraft(input) {
|
|
|
16479
17153
|
for (const spec of specs) {
|
|
16480
17154
|
let source;
|
|
16481
17155
|
try {
|
|
16482
|
-
source =
|
|
17156
|
+
source = fs43.readFileSync(spec.file, "utf-8");
|
|
16483
17157
|
} catch {
|
|
16484
17158
|
continue;
|
|
16485
17159
|
}
|
|
@@ -16599,13 +17273,13 @@ async function handleSpecCraft(input) {
|
|
|
16599
17273
|
}
|
|
16600
17274
|
|
|
16601
17275
|
// src/copy-craft/index.ts
|
|
16602
|
-
import * as
|
|
16603
|
-
import * as
|
|
17276
|
+
import * as fs45 from "fs";
|
|
17277
|
+
import * as path48 from "path";
|
|
16604
17278
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
16605
17279
|
|
|
16606
17280
|
// src/copy-craft/extract/source.ts
|
|
16607
17281
|
import ts5 from "typescript";
|
|
16608
|
-
import * as
|
|
17282
|
+
import * as path46 from "path";
|
|
16609
17283
|
var LOG_LEVELS = ["log", "info", "warn", "warning", "error", "debug", "trace", "fatal"];
|
|
16610
17284
|
function extractFromSource(input) {
|
|
16611
17285
|
if (!/\.(?:ts|tsx|js|jsx)$/i.test(input.file)) return [];
|
|
@@ -16745,7 +17419,7 @@ function isLikelyLogger(name) {
|
|
|
16745
17419
|
}
|
|
16746
17420
|
function fileMatchesCliPaths(file, cliPaths) {
|
|
16747
17421
|
const paths = cliPaths ?? defaultCliOutputPaths();
|
|
16748
|
-
const normalized = file.split(
|
|
17422
|
+
const normalized = file.split(path46.sep).join("/");
|
|
16749
17423
|
return paths.some((p) => normalized.includes(p));
|
|
16750
17424
|
}
|
|
16751
17425
|
function defaultCliOutputPaths() {
|
|
@@ -16765,8 +17439,8 @@ function cleanCommentText(raw) {
|
|
|
16765
17439
|
|
|
16766
17440
|
// src/copy-craft/extract/commits.ts
|
|
16767
17441
|
import { spawnSync } from "child_process";
|
|
16768
|
-
import * as
|
|
16769
|
-
import * as
|
|
17442
|
+
import * as fs44 from "fs";
|
|
17443
|
+
import * as path47 from "path";
|
|
16770
17444
|
var GIT_TIMEOUT_MS = 1e4;
|
|
16771
17445
|
function extractCommits(input) {
|
|
16772
17446
|
const { projectRoot, since = "1 month ago", limit = 100 } = input;
|
|
@@ -16805,8 +17479,8 @@ function extractCommits(input) {
|
|
|
16805
17479
|
function isGitRepo(projectRoot) {
|
|
16806
17480
|
let dir = projectRoot;
|
|
16807
17481
|
for (let i = 0; i < 10; i++) {
|
|
16808
|
-
if (
|
|
16809
|
-
const parent =
|
|
17482
|
+
if (fs44.existsSync(path47.join(dir, ".git"))) return true;
|
|
17483
|
+
const parent = path47.dirname(dir);
|
|
16810
17484
|
if (parent === dir) break;
|
|
16811
17485
|
dir = parent;
|
|
16812
17486
|
}
|
|
@@ -17095,79 +17769,24 @@ async function runCopyCraft(input) {
|
|
|
17095
17769
|
const rubrics = SEED_RUBRICS4;
|
|
17096
17770
|
const enabledSurfaces = new Set(input.surfaces ?? ALL_SURFACES);
|
|
17097
17771
|
const items = [];
|
|
17098
|
-
const counts = {
|
|
17099
|
-
error: 0,
|
|
17100
|
-
log: 0,
|
|
17101
|
-
"cli-output": 0,
|
|
17102
|
-
commit: 0,
|
|
17103
|
-
"pr-description": 0,
|
|
17104
|
-
comment: 0
|
|
17105
|
-
};
|
|
17106
17772
|
const skippedSurfaces = [];
|
|
17107
17773
|
const sourceSurfaces = ["error", "log", "cli-output", "comment"].filter(
|
|
17108
17774
|
(s) => enabledSurfaces.has(s)
|
|
17109
17775
|
);
|
|
17110
17776
|
if (sourceSurfaces.length > 0) {
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
source,
|
|
17122
|
-
surfaces: sourceSurfaces,
|
|
17123
|
-
...input.cliOutputPaths !== void 0 && { cliOutputPaths: input.cliOutputPaths }
|
|
17124
|
-
});
|
|
17125
|
-
const capped = extracted.slice(0, maxItemsPerFile);
|
|
17126
|
-
for (const item of capped) {
|
|
17127
|
-
counts[item.surface]++;
|
|
17128
|
-
items.push(item);
|
|
17129
|
-
}
|
|
17130
|
-
}
|
|
17131
|
-
}
|
|
17132
|
-
if (enabledSurfaces.has("commit")) {
|
|
17133
|
-
const result = extractCommits({
|
|
17134
|
-
projectRoot,
|
|
17135
|
-
...input.commitsSince !== void 0 && { since: input.commitsSince }
|
|
17136
|
-
});
|
|
17137
|
-
if (result.skipReason !== void 0) {
|
|
17138
|
-
skippedSurfaces.push({ surface: "commit", reason: result.skipReason });
|
|
17139
|
-
} else {
|
|
17140
|
-
for (const item of result.items) {
|
|
17141
|
-
counts.commit++;
|
|
17142
|
-
items.push(item);
|
|
17143
|
-
}
|
|
17144
|
-
}
|
|
17145
|
-
}
|
|
17146
|
-
if (enabledSurfaces.has("pr-description")) {
|
|
17147
|
-
const result = extractPRDescriptions({
|
|
17148
|
-
projectRoot,
|
|
17149
|
-
...input.prLimit !== void 0 && { limit: input.prLimit }
|
|
17150
|
-
});
|
|
17151
|
-
if (result.skipReason !== void 0) {
|
|
17152
|
-
skippedSurfaces.push({ surface: "pr-description", reason: result.skipReason });
|
|
17153
|
-
} else {
|
|
17154
|
-
for (const item of result.items) {
|
|
17155
|
-
counts["pr-description"]++;
|
|
17156
|
-
items.push(item);
|
|
17157
|
-
}
|
|
17158
|
-
}
|
|
17159
|
-
}
|
|
17160
|
-
const findings = [];
|
|
17161
|
-
for (const item of items) {
|
|
17162
|
-
for (const rubric of rubrics) {
|
|
17163
|
-
if (!rubricApplies2(rubric, item.surface)) continue;
|
|
17164
|
-
try {
|
|
17165
|
-
const finding = await critiqueOne3({ item, rubric, provider });
|
|
17166
|
-
if (finding !== null) findings.push(finding);
|
|
17167
|
-
} catch {
|
|
17168
|
-
}
|
|
17169
|
-
}
|
|
17777
|
+
items.push(
|
|
17778
|
+
...collectSourceItems({
|
|
17779
|
+
projectRoot,
|
|
17780
|
+
files: input.files,
|
|
17781
|
+
sourceSurfaces,
|
|
17782
|
+
maxFiles,
|
|
17783
|
+
maxItemsPerFile,
|
|
17784
|
+
cliOutputPaths: input.cliOutputPaths
|
|
17785
|
+
})
|
|
17786
|
+
);
|
|
17170
17787
|
}
|
|
17788
|
+
collectGitItems(input, projectRoot, enabledSurfaces, items, skippedSurfaces);
|
|
17789
|
+
const findings = await critiqueItems(items, rubrics, provider);
|
|
17171
17790
|
const surfacesScanned = ALL_SURFACES.filter(
|
|
17172
17791
|
(s) => enabledSurfaces.has(s) && !skippedSurfaces.some((sk) => sk.surface === s)
|
|
17173
17792
|
);
|
|
@@ -17188,15 +17807,92 @@ async function runCopyCraft(input) {
|
|
|
17188
17807
|
rubricsApplied: rubrics.map((r) => r.id),
|
|
17189
17808
|
surfacesScanned
|
|
17190
17809
|
},
|
|
17191
|
-
counts,
|
|
17810
|
+
counts: tallyCounts(items),
|
|
17192
17811
|
skippedSurfaces,
|
|
17193
17812
|
runId: randomUUID5()
|
|
17194
17813
|
}
|
|
17195
17814
|
};
|
|
17196
17815
|
}
|
|
17816
|
+
function collectSourceItems(args) {
|
|
17817
|
+
const out = [];
|
|
17818
|
+
const files = collectSourceFiles(args.projectRoot, args.files).slice(0, args.maxFiles);
|
|
17819
|
+
for (const file of files) {
|
|
17820
|
+
let source;
|
|
17821
|
+
try {
|
|
17822
|
+
source = fs45.readFileSync(file, "utf-8");
|
|
17823
|
+
} catch {
|
|
17824
|
+
continue;
|
|
17825
|
+
}
|
|
17826
|
+
const extracted = extractFromSource({
|
|
17827
|
+
file,
|
|
17828
|
+
source,
|
|
17829
|
+
surfaces: args.sourceSurfaces,
|
|
17830
|
+
...args.cliOutputPaths !== void 0 && { cliOutputPaths: args.cliOutputPaths }
|
|
17831
|
+
});
|
|
17832
|
+
out.push(...extracted.slice(0, args.maxItemsPerFile));
|
|
17833
|
+
}
|
|
17834
|
+
return out;
|
|
17835
|
+
}
|
|
17836
|
+
function collectExtractionResult(result, surface, items, skippedSurfaces) {
|
|
17837
|
+
if (result.skipReason !== void 0) {
|
|
17838
|
+
skippedSurfaces.push({ surface, reason: result.skipReason });
|
|
17839
|
+
} else {
|
|
17840
|
+
items.push(...result.items);
|
|
17841
|
+
}
|
|
17842
|
+
}
|
|
17843
|
+
function collectGitItems(input, projectRoot, enabledSurfaces, items, skippedSurfaces) {
|
|
17844
|
+
if (enabledSurfaces.has("commit")) {
|
|
17845
|
+
collectExtractionResult(
|
|
17846
|
+
extractCommits({
|
|
17847
|
+
projectRoot,
|
|
17848
|
+
...input.commitsSince !== void 0 && { since: input.commitsSince }
|
|
17849
|
+
}),
|
|
17850
|
+
"commit",
|
|
17851
|
+
items,
|
|
17852
|
+
skippedSurfaces
|
|
17853
|
+
);
|
|
17854
|
+
}
|
|
17855
|
+
if (enabledSurfaces.has("pr-description")) {
|
|
17856
|
+
collectExtractionResult(
|
|
17857
|
+
extractPRDescriptions({
|
|
17858
|
+
projectRoot,
|
|
17859
|
+
...input.prLimit !== void 0 && { limit: input.prLimit }
|
|
17860
|
+
}),
|
|
17861
|
+
"pr-description",
|
|
17862
|
+
items,
|
|
17863
|
+
skippedSurfaces
|
|
17864
|
+
);
|
|
17865
|
+
}
|
|
17866
|
+
}
|
|
17867
|
+
async function critiqueItems(items, rubrics, provider) {
|
|
17868
|
+
const findings = [];
|
|
17869
|
+
for (const item of items) {
|
|
17870
|
+
for (const rubric of rubrics) {
|
|
17871
|
+
if (!rubricApplies2(rubric, item.surface)) continue;
|
|
17872
|
+
try {
|
|
17873
|
+
const finding = await critiqueOne3({ item, rubric, provider });
|
|
17874
|
+
if (finding !== null) findings.push(finding);
|
|
17875
|
+
} catch {
|
|
17876
|
+
}
|
|
17877
|
+
}
|
|
17878
|
+
}
|
|
17879
|
+
return findings;
|
|
17880
|
+
}
|
|
17881
|
+
function tallyCounts(items) {
|
|
17882
|
+
const counts = {
|
|
17883
|
+
error: 0,
|
|
17884
|
+
log: 0,
|
|
17885
|
+
"cli-output": 0,
|
|
17886
|
+
commit: 0,
|
|
17887
|
+
"pr-description": 0,
|
|
17888
|
+
comment: 0
|
|
17889
|
+
};
|
|
17890
|
+
for (const item of items) counts[item.surface]++;
|
|
17891
|
+
return counts;
|
|
17892
|
+
}
|
|
17197
17893
|
function collectSourceFiles(projectRoot, explicitFiles) {
|
|
17198
17894
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
17199
|
-
return explicitFiles.map((f) =>
|
|
17895
|
+
return explicitFiles.map((f) => path48.isAbsolute(f) ? f : path48.join(projectRoot, f));
|
|
17200
17896
|
}
|
|
17201
17897
|
const out = [];
|
|
17202
17898
|
walk6(projectRoot, out, 0);
|
|
@@ -17206,7 +17902,7 @@ function walk6(dir, out, depth) {
|
|
|
17206
17902
|
if (depth > 8) return;
|
|
17207
17903
|
let entries;
|
|
17208
17904
|
try {
|
|
17209
|
-
entries =
|
|
17905
|
+
entries = fs45.readdirSync(dir, { withFileTypes: true });
|
|
17210
17906
|
} catch {
|
|
17211
17907
|
return;
|
|
17212
17908
|
}
|
|
@@ -17214,7 +17910,7 @@ function walk6(dir, out, depth) {
|
|
|
17214
17910
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
17215
17911
|
continue;
|
|
17216
17912
|
}
|
|
17217
|
-
const full =
|
|
17913
|
+
const full = path48.join(dir, entry.name);
|
|
17218
17914
|
if (entry.isDirectory()) walk6(full, out, depth + 1);
|
|
17219
17915
|
else if (entry.isFile() && SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext)))
|
|
17220
17916
|
out.push(full);
|
|
@@ -17284,8 +17980,8 @@ async function handleCopyCraft(input) {
|
|
|
17284
17980
|
}
|
|
17285
17981
|
|
|
17286
17982
|
// src/test-craft/index.ts
|
|
17287
|
-
import * as
|
|
17288
|
-
import * as
|
|
17983
|
+
import * as fs47 from "fs";
|
|
17984
|
+
import * as path50 from "path";
|
|
17289
17985
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
17290
17986
|
|
|
17291
17987
|
// src/test-craft/extract/framework.ts
|
|
@@ -17391,25 +18087,25 @@ function extractCallbackText(node, fullSource) {
|
|
|
17391
18087
|
}
|
|
17392
18088
|
|
|
17393
18089
|
// src/test-craft/extract/source-pair.ts
|
|
17394
|
-
import * as
|
|
17395
|
-
import * as
|
|
18090
|
+
import * as fs46 from "fs";
|
|
18091
|
+
import * as path49 from "path";
|
|
17396
18092
|
var MAX_SOURCE_CHARS = 2e3;
|
|
17397
18093
|
function resolveSourceFile(testFile) {
|
|
17398
|
-
const dir =
|
|
18094
|
+
const dir = path49.dirname(testFile);
|
|
17399
18095
|
const ext = /\.tsx$/.test(testFile) ? ".tsx" : ".ts";
|
|
17400
|
-
const base =
|
|
18096
|
+
const base = path49.basename(testFile).replace(/\.(?:test|spec)\.(?:tsx?|jsx?)$/, "");
|
|
17401
18097
|
const candidates = [
|
|
17402
18098
|
// Sibling (most common in co-located setups)
|
|
17403
|
-
|
|
18099
|
+
path49.join(dir, base + ext),
|
|
17404
18100
|
// tests/ peer to src/
|
|
17405
|
-
|
|
18101
|
+
path49.join(dir, "..", "src", base + ext),
|
|
17406
18102
|
// tests/ one level deeper than src/
|
|
17407
|
-
|
|
18103
|
+
path49.join(dir, "..", "..", "src", base + ext)
|
|
17408
18104
|
];
|
|
17409
18105
|
for (const candidate of candidates) {
|
|
17410
|
-
if (
|
|
18106
|
+
if (fs46.existsSync(candidate)) {
|
|
17411
18107
|
try {
|
|
17412
|
-
const raw =
|
|
18108
|
+
const raw = fs46.readFileSync(candidate, "utf-8");
|
|
17413
18109
|
const content = raw.length > MAX_SOURCE_CHARS ? raw.slice(0, MAX_SOURCE_CHARS) + "\n[\u2026truncated for prompt cost\u2026]" : raw;
|
|
17414
18110
|
return { file: candidate, content };
|
|
17415
18111
|
} catch {
|
|
@@ -17631,57 +18327,15 @@ async function runTestCraft(input) {
|
|
|
17631
18327
|
const sourcePairEnabled = input.sourcePair !== false;
|
|
17632
18328
|
const frameworksFilter = input.frameworks !== void 0 ? new Set(input.frameworks) : null;
|
|
17633
18329
|
const files = collectTestFiles(projectRoot, input.files).slice(0, maxFiles);
|
|
17634
|
-
const
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
playwright: 0,
|
|
17640
|
-
unknown: 0
|
|
17641
|
-
};
|
|
17642
|
-
let testsSkippedOrTodo = 0;
|
|
17643
|
-
let sourcePairedCount = 0;
|
|
17644
|
-
const sourcePairCache = /* @__PURE__ */ new Map();
|
|
17645
|
-
for (const file of files) {
|
|
17646
|
-
let source;
|
|
17647
|
-
try {
|
|
17648
|
-
source = fs44.readFileSync(file, "utf-8");
|
|
17649
|
-
} catch {
|
|
17650
|
-
continue;
|
|
17651
|
-
}
|
|
17652
|
-
const framework = detectFramework(source);
|
|
17653
|
-
if (frameworksFilter !== null && !frameworksFilter.has(framework)) continue;
|
|
17654
|
-
frameworksDetected[framework]++;
|
|
17655
|
-
const extracted = extractTests({ file, source, framework });
|
|
17656
|
-
const capped = extracted.slice(0, maxTestsPerFile);
|
|
17657
|
-
for (const test of capped) {
|
|
17658
|
-
if (test.todo) {
|
|
17659
|
-
testsSkippedOrTodo++;
|
|
17660
|
-
continue;
|
|
17661
|
-
}
|
|
17662
|
-
allTests.push(test);
|
|
17663
|
-
}
|
|
17664
|
-
if (sourcePairEnabled && !sourcePairCache.has(file)) {
|
|
17665
|
-
const result = resolveSourceFile(file);
|
|
17666
|
-
sourcePairCache.set(file, result);
|
|
17667
|
-
if (result !== null) sourcePairedCount++;
|
|
17668
|
-
}
|
|
17669
|
-
}
|
|
18330
|
+
const extraction = extractTestsFromFiles(files, {
|
|
18331
|
+
frameworksFilter,
|
|
18332
|
+
maxTestsPerFile,
|
|
18333
|
+
sourcePairEnabled
|
|
18334
|
+
});
|
|
17670
18335
|
const findings = [];
|
|
17671
|
-
for (const test of allTests) {
|
|
17672
|
-
const pair = sourcePairEnabled ? sourcePairCache.get(test.file) ?? null : null;
|
|
17673
|
-
|
|
17674
|
-
try {
|
|
17675
|
-
const finding = await critiqueOne4({
|
|
17676
|
-
test,
|
|
17677
|
-
rubric,
|
|
17678
|
-
provider,
|
|
17679
|
-
...pair !== null ? { sourcePair: pair } : {}
|
|
17680
|
-
});
|
|
17681
|
-
if (finding !== null) findings.push(finding);
|
|
17682
|
-
} catch {
|
|
17683
|
-
}
|
|
17684
|
-
}
|
|
18336
|
+
for (const test of extraction.allTests) {
|
|
18337
|
+
const pair = sourcePairEnabled ? extraction.sourcePairCache.get(test.file) ?? null : null;
|
|
18338
|
+
findings.push(...await critiqueTest(test, rubrics, provider, pair));
|
|
17685
18339
|
}
|
|
17686
18340
|
const totalCost = sumCosts4(provider);
|
|
17687
18341
|
return {
|
|
@@ -17699,18 +18353,83 @@ async function runTestCraft(input) {
|
|
|
17699
18353
|
catalog: { rubricsApplied: rubrics.map((r) => r.id) },
|
|
17700
18354
|
counts: {
|
|
17701
18355
|
filesScanned: files.length,
|
|
17702
|
-
testsExtracted: allTests.length,
|
|
17703
|
-
testsSkippedOrTodo,
|
|
17704
|
-
sourcePaired: sourcePairedCount
|
|
18356
|
+
testsExtracted: extraction.allTests.length,
|
|
18357
|
+
testsSkippedOrTodo: extraction.testsSkippedOrTodo,
|
|
18358
|
+
sourcePaired: extraction.sourcePairedCount
|
|
17705
18359
|
},
|
|
17706
|
-
frameworksDetected,
|
|
18360
|
+
frameworksDetected: extraction.frameworksDetected,
|
|
17707
18361
|
runId: randomUUID6()
|
|
17708
18362
|
}
|
|
17709
18363
|
};
|
|
17710
18364
|
}
|
|
18365
|
+
function readFileOrNull(file) {
|
|
18366
|
+
try {
|
|
18367
|
+
return fs47.readFileSync(file, "utf-8");
|
|
18368
|
+
} catch {
|
|
18369
|
+
return null;
|
|
18370
|
+
}
|
|
18371
|
+
}
|
|
18372
|
+
function collectNonTodoTests(tests, out) {
|
|
18373
|
+
let skipped = 0;
|
|
18374
|
+
for (const test of tests) {
|
|
18375
|
+
if (test.todo) skipped++;
|
|
18376
|
+
else out.push(test);
|
|
18377
|
+
}
|
|
18378
|
+
return skipped;
|
|
18379
|
+
}
|
|
18380
|
+
function extractTestsFromFiles(files, opts) {
|
|
18381
|
+
const allTests = [];
|
|
18382
|
+
const frameworksDetected = {
|
|
18383
|
+
vitest: 0,
|
|
18384
|
+
jest: 0,
|
|
18385
|
+
mocha: 0,
|
|
18386
|
+
playwright: 0,
|
|
18387
|
+
unknown: 0
|
|
18388
|
+
};
|
|
18389
|
+
let testsSkippedOrTodo = 0;
|
|
18390
|
+
let sourcePairedCount = 0;
|
|
18391
|
+
const sourcePairCache = /* @__PURE__ */ new Map();
|
|
18392
|
+
for (const file of files) {
|
|
18393
|
+
const source = readFileOrNull(file);
|
|
18394
|
+
if (source === null) continue;
|
|
18395
|
+
const framework = detectFramework(source);
|
|
18396
|
+
if (opts.frameworksFilter !== null && !opts.frameworksFilter.has(framework)) continue;
|
|
18397
|
+
frameworksDetected[framework]++;
|
|
18398
|
+
const capped = extractTests({ file, source, framework }).slice(0, opts.maxTestsPerFile);
|
|
18399
|
+
testsSkippedOrTodo += collectNonTodoTests(capped, allTests);
|
|
18400
|
+
if (opts.sourcePairEnabled && !sourcePairCache.has(file)) {
|
|
18401
|
+
const result = resolveSourceFile(file);
|
|
18402
|
+
sourcePairCache.set(file, result);
|
|
18403
|
+
if (result !== null) sourcePairedCount++;
|
|
18404
|
+
}
|
|
18405
|
+
}
|
|
18406
|
+
return {
|
|
18407
|
+
allTests,
|
|
18408
|
+
frameworksDetected,
|
|
18409
|
+
testsSkippedOrTodo,
|
|
18410
|
+
sourcePairedCount,
|
|
18411
|
+
sourcePairCache
|
|
18412
|
+
};
|
|
18413
|
+
}
|
|
18414
|
+
async function critiqueTest(test, rubrics, provider, pair) {
|
|
18415
|
+
const findings = [];
|
|
18416
|
+
for (const rubric of rubrics) {
|
|
18417
|
+
try {
|
|
18418
|
+
const finding = await critiqueOne4({
|
|
18419
|
+
test,
|
|
18420
|
+
rubric,
|
|
18421
|
+
provider,
|
|
18422
|
+
...pair !== null ? { sourcePair: pair } : {}
|
|
18423
|
+
});
|
|
18424
|
+
if (finding !== null) findings.push(finding);
|
|
18425
|
+
} catch {
|
|
18426
|
+
}
|
|
18427
|
+
}
|
|
18428
|
+
return findings;
|
|
18429
|
+
}
|
|
17711
18430
|
function collectTestFiles(projectRoot, explicitFiles) {
|
|
17712
18431
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
17713
|
-
return explicitFiles.map((f) =>
|
|
18432
|
+
return explicitFiles.map((f) => path50.isAbsolute(f) ? f : path50.join(projectRoot, f));
|
|
17714
18433
|
}
|
|
17715
18434
|
const out = [];
|
|
17716
18435
|
walk7(projectRoot, out, 0);
|
|
@@ -17720,7 +18439,7 @@ function walk7(dir, out, depth) {
|
|
|
17720
18439
|
if (depth > 10) return;
|
|
17721
18440
|
let entries;
|
|
17722
18441
|
try {
|
|
17723
|
-
entries =
|
|
18442
|
+
entries = fs47.readdirSync(dir, { withFileTypes: true });
|
|
17724
18443
|
} catch {
|
|
17725
18444
|
return;
|
|
17726
18445
|
}
|
|
@@ -17728,7 +18447,7 @@ function walk7(dir, out, depth) {
|
|
|
17728
18447
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
17729
18448
|
continue;
|
|
17730
18449
|
}
|
|
17731
|
-
const full =
|
|
18450
|
+
const full = path50.join(dir, entry.name);
|
|
17732
18451
|
if (entry.isDirectory()) walk7(full, out, depth + 1);
|
|
17733
18452
|
else if (entry.isFile() && TEST_FILE_EXTS.some((ext) => entry.name.endsWith(ext))) {
|
|
17734
18453
|
out.push(full);
|
|
@@ -17801,18 +18520,18 @@ async function handleTestCraft(input) {
|
|
|
17801
18520
|
}
|
|
17802
18521
|
|
|
17803
18522
|
// src/knowledge-craft/index.ts
|
|
17804
|
-
import * as
|
|
17805
|
-
import * as
|
|
18523
|
+
import * as fs49 from "fs";
|
|
18524
|
+
import * as path52 from "path";
|
|
17806
18525
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
17807
18526
|
|
|
17808
18527
|
// src/knowledge-craft/extract/discover.ts
|
|
17809
|
-
import * as
|
|
17810
|
-
import * as
|
|
18528
|
+
import * as fs48 from "fs";
|
|
18529
|
+
import * as path51 from "path";
|
|
17811
18530
|
var KNOWLEDGE_ROOT = "docs/knowledge";
|
|
17812
18531
|
var DEFAULT_EXCLUDED_DIRS = ["decisions"];
|
|
17813
18532
|
function discoverKnowledgeEntries(projectRoot, extraExcludeDirs) {
|
|
17814
|
-
const root =
|
|
17815
|
-
if (!
|
|
18533
|
+
const root = path51.join(projectRoot, KNOWLEDGE_ROOT);
|
|
18534
|
+
if (!fs48.existsSync(root)) return [];
|
|
17816
18535
|
const exclude = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDED_DIRS, ...extraExcludeDirs ?? []]);
|
|
17817
18536
|
const out = [];
|
|
17818
18537
|
walk8(root, root, out, exclude);
|
|
@@ -17821,13 +18540,13 @@ function discoverKnowledgeEntries(projectRoot, extraExcludeDirs) {
|
|
|
17821
18540
|
function walk8(dir, root, out, exclude) {
|
|
17822
18541
|
let entries;
|
|
17823
18542
|
try {
|
|
17824
|
-
entries =
|
|
18543
|
+
entries = fs48.readdirSync(dir, { withFileTypes: true });
|
|
17825
18544
|
} catch {
|
|
17826
18545
|
return;
|
|
17827
18546
|
}
|
|
17828
18547
|
for (const entry of entries) {
|
|
17829
18548
|
if (entry.name.startsWith(".")) continue;
|
|
17830
|
-
const full =
|
|
18549
|
+
const full = path51.join(dir, entry.name);
|
|
17831
18550
|
if (entry.isDirectory()) {
|
|
17832
18551
|
if (exclude.has(entry.name)) continue;
|
|
17833
18552
|
walk8(full, root, out, exclude);
|
|
@@ -17836,7 +18555,7 @@ function walk8(dir, root, out, exclude) {
|
|
|
17836
18555
|
if (!entry.isFile()) continue;
|
|
17837
18556
|
if (!entry.name.endsWith(".md")) continue;
|
|
17838
18557
|
if (entry.name.toLowerCase() === "readme.md") continue;
|
|
17839
|
-
const rel =
|
|
18558
|
+
const rel = path51.relative(root, full).replaceAll("\\", "/");
|
|
17840
18559
|
out.push({ file: full, relative: rel });
|
|
17841
18560
|
}
|
|
17842
18561
|
}
|
|
@@ -18016,7 +18735,7 @@ async function runKnowledgeCraft(input) {
|
|
|
18016
18735
|
for (const entry of entries) {
|
|
18017
18736
|
let content;
|
|
18018
18737
|
try {
|
|
18019
|
-
content =
|
|
18738
|
+
content = fs49.readFileSync(entry.file, "utf-8");
|
|
18020
18739
|
} catch {
|
|
18021
18740
|
filesSkipped++;
|
|
18022
18741
|
continue;
|
|
@@ -18057,10 +18776,10 @@ async function runKnowledgeCraft(input) {
|
|
|
18057
18776
|
}
|
|
18058
18777
|
function collectEntries(projectRoot, input) {
|
|
18059
18778
|
if (input.files !== void 0 && input.files.length > 0) {
|
|
18060
|
-
const root =
|
|
18779
|
+
const root = path52.join(projectRoot, KNOWLEDGE_ROOT);
|
|
18061
18780
|
return input.files.map((f) => ({
|
|
18062
18781
|
file: f,
|
|
18063
|
-
relative:
|
|
18782
|
+
relative: path52.relative(root, f).replaceAll("\\", "/")
|
|
18064
18783
|
}));
|
|
18065
18784
|
}
|
|
18066
18785
|
return discoverKnowledgeEntries(projectRoot, input.excludeDirs);
|
|
@@ -18122,12 +18841,12 @@ async function handleKnowledgeCraft(input) {
|
|
|
18122
18841
|
}
|
|
18123
18842
|
|
|
18124
18843
|
// src/security-craft/index.ts
|
|
18125
|
-
import * as
|
|
18844
|
+
import * as fs51 from "fs";
|
|
18126
18845
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
18127
18846
|
|
|
18128
18847
|
// src/security-craft/extract/discover.ts
|
|
18129
|
-
import * as
|
|
18130
|
-
import * as
|
|
18848
|
+
import * as fs50 from "fs";
|
|
18849
|
+
import * as path53 from "path";
|
|
18131
18850
|
var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
18132
18851
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
18133
18852
|
"node_modules",
|
|
@@ -18147,12 +18866,12 @@ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
|
18147
18866
|
]);
|
|
18148
18867
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs)$/i;
|
|
18149
18868
|
function discoverSourceFiles(projectRoot, packagesFilter) {
|
|
18150
|
-
const packagesDir =
|
|
18151
|
-
if (!
|
|
18869
|
+
const packagesDir = path53.join(projectRoot, "packages");
|
|
18870
|
+
if (!fs50.existsSync(packagesDir)) return [];
|
|
18152
18871
|
const out = [];
|
|
18153
18872
|
let entries;
|
|
18154
18873
|
try {
|
|
18155
|
-
entries =
|
|
18874
|
+
entries = fs50.readdirSync(packagesDir, { withFileTypes: true });
|
|
18156
18875
|
} catch {
|
|
18157
18876
|
return [];
|
|
18158
18877
|
}
|
|
@@ -18160,8 +18879,8 @@ function discoverSourceFiles(projectRoot, packagesFilter) {
|
|
|
18160
18879
|
if (!entry.isDirectory()) continue;
|
|
18161
18880
|
if (entry.name.startsWith(".")) continue;
|
|
18162
18881
|
if (packagesFilter !== void 0 && !packagesFilter.includes(entry.name)) continue;
|
|
18163
|
-
const srcDir =
|
|
18164
|
-
if (!
|
|
18882
|
+
const srcDir = path53.join(packagesDir, entry.name, "src");
|
|
18883
|
+
if (!fs50.existsSync(srcDir)) continue;
|
|
18165
18884
|
walk9(srcDir, out);
|
|
18166
18885
|
}
|
|
18167
18886
|
return out;
|
|
@@ -18169,13 +18888,13 @@ function discoverSourceFiles(projectRoot, packagesFilter) {
|
|
|
18169
18888
|
function walk9(dir, out) {
|
|
18170
18889
|
let entries;
|
|
18171
18890
|
try {
|
|
18172
|
-
entries =
|
|
18891
|
+
entries = fs50.readdirSync(dir, { withFileTypes: true });
|
|
18173
18892
|
} catch {
|
|
18174
18893
|
return;
|
|
18175
18894
|
}
|
|
18176
18895
|
for (const entry of entries) {
|
|
18177
18896
|
if (entry.name.startsWith(".")) continue;
|
|
18178
|
-
const full =
|
|
18897
|
+
const full = path53.join(dir, entry.name);
|
|
18179
18898
|
if (entry.isDirectory()) {
|
|
18180
18899
|
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
18181
18900
|
walk9(full, out);
|
|
@@ -18183,7 +18902,7 @@ function walk9(dir, out) {
|
|
|
18183
18902
|
}
|
|
18184
18903
|
if (!entry.isFile()) continue;
|
|
18185
18904
|
if (TEST_FILE_PATTERN.test(entry.name)) continue;
|
|
18186
|
-
const ext =
|
|
18905
|
+
const ext = path53.extname(entry.name);
|
|
18187
18906
|
if (!SOURCE_EXTENSIONS2.has(ext)) continue;
|
|
18188
18907
|
out.push(full);
|
|
18189
18908
|
}
|
|
@@ -18437,21 +19156,30 @@ function findSecretMarker(node) {
|
|
|
18437
19156
|
if (ts7.isIdentifier(node) && SECRET_NAME_PATTERN.test(node.text)) return node.text;
|
|
18438
19157
|
if (ts7.isPropertyAccessExpression(node) && ts7.isIdentifier(node.name) && SECRET_NAME_PATTERN.test(node.name.text))
|
|
18439
19158
|
return node.name.text;
|
|
18440
|
-
if (ts7.isTemplateExpression(node))
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
19159
|
+
if (ts7.isTemplateExpression(node)) return findSecretInTemplate(node);
|
|
19160
|
+
if (ts7.isObjectLiteralExpression(node)) return findSecretInObjectLiteral(node);
|
|
19161
|
+
return void 0;
|
|
19162
|
+
}
|
|
19163
|
+
function findSecretInTemplate(node) {
|
|
19164
|
+
for (const span of node.templateSpans) {
|
|
19165
|
+
const sub = findSecretMarker(span.expression);
|
|
19166
|
+
if (sub !== void 0) return sub;
|
|
18445
19167
|
}
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
|
|
19168
|
+
return void 0;
|
|
19169
|
+
}
|
|
19170
|
+
function findSecretInObjectLiteral(node) {
|
|
19171
|
+
for (const prop of node.properties) {
|
|
19172
|
+
const marker = secretMarkerFromProperty(prop);
|
|
19173
|
+
if (marker !== void 0) return marker;
|
|
19174
|
+
}
|
|
19175
|
+
return void 0;
|
|
19176
|
+
}
|
|
19177
|
+
function secretMarkerFromProperty(prop) {
|
|
19178
|
+
if (ts7.isPropertyAssignment(prop) && ts7.isIdentifier(prop.name) && SECRET_NAME_PATTERN.test(prop.name.text)) {
|
|
19179
|
+
return prop.name.text;
|
|
19180
|
+
}
|
|
19181
|
+
if (ts7.isShorthandPropertyAssignment(prop) && SECRET_NAME_PATTERN.test(prop.name.text)) {
|
|
19182
|
+
return prop.name.text;
|
|
18455
19183
|
}
|
|
18456
19184
|
return void 0;
|
|
18457
19185
|
}
|
|
@@ -18623,21 +19351,28 @@ function sliceAroundLine(source, line, maxChars) {
|
|
|
18623
19351
|
if (source.length <= maxChars) return source;
|
|
18624
19352
|
const lines = source.split("\n");
|
|
18625
19353
|
const targetIdx = Math.max(0, Math.min(lines.length - 1, line - 1));
|
|
19354
|
+
const { lo, hi } = expandWindow(lines, targetIdx, maxChars);
|
|
19355
|
+
return lines.slice(lo, hi + 1).join("\n");
|
|
19356
|
+
}
|
|
19357
|
+
function lineLen(lines, idx) {
|
|
19358
|
+
return lines[idx]?.length ?? 0;
|
|
19359
|
+
}
|
|
19360
|
+
function expandWindow(lines, targetIdx, maxChars) {
|
|
18626
19361
|
let lo = targetIdx;
|
|
18627
19362
|
let hi = targetIdx;
|
|
18628
|
-
let len = lines
|
|
19363
|
+
let len = lineLen(lines, targetIdx);
|
|
18629
19364
|
while (len < maxChars && (lo > 0 || hi < lines.length - 1)) {
|
|
18630
19365
|
if (lo > 0) {
|
|
18631
19366
|
lo--;
|
|
18632
|
-
len += (lines
|
|
19367
|
+
len += lineLen(lines, lo) + 1;
|
|
18633
19368
|
if (len >= maxChars) break;
|
|
18634
19369
|
}
|
|
18635
19370
|
if (hi < lines.length - 1) {
|
|
18636
19371
|
hi++;
|
|
18637
|
-
len += (lines
|
|
19372
|
+
len += lineLen(lines, hi) + 1;
|
|
18638
19373
|
}
|
|
18639
19374
|
}
|
|
18640
|
-
return
|
|
19375
|
+
return { lo, hi };
|
|
18641
19376
|
}
|
|
18642
19377
|
function parseFencedJson6(raw) {
|
|
18643
19378
|
const match = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
|
|
@@ -18680,7 +19415,7 @@ async function runSecurityCraft(input) {
|
|
|
18680
19415
|
for (const file of candidateFiles) {
|
|
18681
19416
|
let source;
|
|
18682
19417
|
try {
|
|
18683
|
-
source =
|
|
19418
|
+
source = fs51.readFileSync(file, "utf-8");
|
|
18684
19419
|
} catch {
|
|
18685
19420
|
continue;
|
|
18686
19421
|
}
|
|
@@ -18832,7 +19567,7 @@ async function resolveAnalysisProvider(model) {
|
|
|
18832
19567
|
}
|
|
18833
19568
|
}
|
|
18834
19569
|
async function emptyGraphStore() {
|
|
18835
|
-
const { GraphStore } = await import("./dist-
|
|
19570
|
+
const { GraphStore } = await import("./dist-DWBTZXR5.js");
|
|
18836
19571
|
return new GraphStore();
|
|
18837
19572
|
}
|
|
18838
19573
|
function validateInput(input) {
|
|
@@ -18888,9 +19623,123 @@ function errorResponse(message) {
|
|
|
18888
19623
|
};
|
|
18889
19624
|
}
|
|
18890
19625
|
|
|
19626
|
+
// src/mcp/tools/acceptance-eval.ts
|
|
19627
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
19628
|
+
var acceptanceEvalDefinition = {
|
|
19629
|
+
name: "acceptance_eval",
|
|
19630
|
+
description: "Pre-execution LLM-judgment: does a spec carry measurable, testable, complete acceptance criteria? The upstream twin of outcome_eval. Reads the spec's success/acceptance section, emits a confidence-rated AcceptanceVerdict (MEASURABLE | NOT_MEASURABLE | INCONCLUSIVE) with criteriaFindings (a, advisory), coverageFindings (b, advisory) and a rationale. Authority is DERIVED in TypeScript, never trusted from the LLM: a high-confidence NOT_MEASURABLE is blocking; every other verdict is advisory. testGlobs/testContent are optional evidence for (b) \u2014 omitting them degrades coverage findings to advisory-empty but never affects the measurability gate.",
|
|
19631
|
+
inputSchema: {
|
|
19632
|
+
type: "object",
|
|
19633
|
+
properties: {
|
|
19634
|
+
specPath: {
|
|
19635
|
+
type: "string",
|
|
19636
|
+
description: "Absolute or repo-relative path to the spec markdown to judge"
|
|
19637
|
+
},
|
|
19638
|
+
testGlobs: {
|
|
19639
|
+
type: "array",
|
|
19640
|
+
items: { type: "string" },
|
|
19641
|
+
description: "Optional globs locating test files; their contents supply the (b) coverage evidence. Ignored when testContent is provided. Absolute globs are recommended; relative globs resolve against the MCP server cwd."
|
|
19642
|
+
},
|
|
19643
|
+
testContent: {
|
|
19644
|
+
type: "string",
|
|
19645
|
+
description: "Optional pre-collected test snippets (the (b) evidence). Takes precedence over testGlobs."
|
|
19646
|
+
},
|
|
19647
|
+
model: {
|
|
19648
|
+
type: "string",
|
|
19649
|
+
description: "Optional model override for the acceptance-eval LLM call"
|
|
19650
|
+
}
|
|
19651
|
+
},
|
|
19652
|
+
required: ["specPath"]
|
|
19653
|
+
}
|
|
19654
|
+
};
|
|
19655
|
+
async function resolveAnalysisProvider2(model) {
|
|
19656
|
+
try {
|
|
19657
|
+
const intelligence = await import("@harness-engineering/intelligence");
|
|
19658
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
19659
|
+
if (!apiKey) return null;
|
|
19660
|
+
const Provider = intelligence.AnthropicAnalysisProvider;
|
|
19661
|
+
if (typeof Provider !== "function") return null;
|
|
19662
|
+
return new Provider(model !== void 0 ? { apiKey, defaultModel: model } : { apiKey });
|
|
19663
|
+
} catch {
|
|
19664
|
+
return null;
|
|
19665
|
+
}
|
|
19666
|
+
}
|
|
19667
|
+
function validateInput2(input) {
|
|
19668
|
+
if (typeof input?.specPath !== "string" || input.specPath.length === 0) {
|
|
19669
|
+
return "acceptance_eval: `specPath` is required";
|
|
19670
|
+
}
|
|
19671
|
+
return null;
|
|
19672
|
+
}
|
|
19673
|
+
async function resolveTestContent(input) {
|
|
19674
|
+
if (typeof input.testContent === "string" && input.testContent.length > 0) {
|
|
19675
|
+
return input.testContent;
|
|
19676
|
+
}
|
|
19677
|
+
if (!Array.isArray(input.testGlobs) || input.testGlobs.length === 0) {
|
|
19678
|
+
return void 0;
|
|
19679
|
+
}
|
|
19680
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19681
|
+
const parts = [];
|
|
19682
|
+
for (const pattern of input.testGlobs) {
|
|
19683
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
19684
|
+
let files;
|
|
19685
|
+
try {
|
|
19686
|
+
files = await findFiles(normalized);
|
|
19687
|
+
} catch {
|
|
19688
|
+
continue;
|
|
19689
|
+
}
|
|
19690
|
+
for (const file of files) {
|
|
19691
|
+
if (seen.has(file)) continue;
|
|
19692
|
+
seen.add(file);
|
|
19693
|
+
try {
|
|
19694
|
+
parts.push(`// ${file}
|
|
19695
|
+
${await readFile2(file, "utf8")}`);
|
|
19696
|
+
} catch {
|
|
19697
|
+
}
|
|
19698
|
+
}
|
|
19699
|
+
}
|
|
19700
|
+
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
19701
|
+
}
|
|
19702
|
+
async function handleAcceptanceEval(input) {
|
|
19703
|
+
const validationError = validateInput2(input);
|
|
19704
|
+
if (validationError !== null) return errorResponse2(validationError);
|
|
19705
|
+
try {
|
|
19706
|
+
const { AcceptanceEvaluator } = await import("@harness-engineering/intelligence");
|
|
19707
|
+
const provider = await resolveAnalysisProvider2(input.model);
|
|
19708
|
+
const evaluator = new AcceptanceEvaluator(
|
|
19709
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19710
|
+
provider ?? unconfiguredProvider2(),
|
|
19711
|
+
input.model !== void 0 ? { model: input.model } : {}
|
|
19712
|
+
);
|
|
19713
|
+
const testContent = await resolveTestContent(input);
|
|
19714
|
+
const verdict = await evaluator.evaluate({
|
|
19715
|
+
specPath: input.specPath,
|
|
19716
|
+
...testContent !== void 0 && { testContent }
|
|
19717
|
+
});
|
|
19718
|
+
return { content: [{ type: "text", text: JSON.stringify(verdict, null, 2) }] };
|
|
19719
|
+
} catch (err2) {
|
|
19720
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
19721
|
+
return errorResponse2(`acceptance_eval failed: ${message}`);
|
|
19722
|
+
}
|
|
19723
|
+
}
|
|
19724
|
+
function unconfiguredProvider2() {
|
|
19725
|
+
return {
|
|
19726
|
+
analyze: () => Promise.reject(
|
|
19727
|
+
new Error(
|
|
19728
|
+
"No analysis provider configured (set ANTHROPIC_API_KEY). Degrading to an inconclusive, advisory verdict."
|
|
19729
|
+
)
|
|
19730
|
+
)
|
|
19731
|
+
};
|
|
19732
|
+
}
|
|
19733
|
+
function errorResponse2(message) {
|
|
19734
|
+
return {
|
|
19735
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
19736
|
+
isError: true
|
|
19737
|
+
};
|
|
19738
|
+
}
|
|
19739
|
+
|
|
18891
19740
|
// src/mcp/tools/strategy.ts
|
|
18892
|
-
import * as
|
|
18893
|
-
import * as
|
|
19741
|
+
import * as fs52 from "fs/promises";
|
|
19742
|
+
import * as path54 from "path";
|
|
18894
19743
|
var STRATEGY_FILENAME = "STRATEGY.md";
|
|
18895
19744
|
function mcpJson(payload, isError = false) {
|
|
18896
19745
|
return { content: [{ type: "text", text: JSON.stringify(payload) }], isError };
|
|
@@ -18916,7 +19765,7 @@ async function handleValidateStrategy(input) {
|
|
|
18916
19765
|
} catch (error) {
|
|
18917
19766
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18918
19767
|
}
|
|
18919
|
-
const { validateStrategy } = await import("./dist-
|
|
19768
|
+
const { validateStrategy } = await import("./dist-6D2F2J57.js");
|
|
18920
19769
|
const result = await validateStrategy(projectPath);
|
|
18921
19770
|
if (!result.ok) {
|
|
18922
19771
|
return mcpJson({ present: true, valid: false, error: result.error.message });
|
|
@@ -18941,7 +19790,7 @@ async function handleReadStrategy(input) {
|
|
|
18941
19790
|
} catch (error) {
|
|
18942
19791
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18943
19792
|
}
|
|
18944
|
-
const core = await import("./dist-
|
|
19793
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
18945
19794
|
const validation = await core.validateStrategy(projectPath);
|
|
18946
19795
|
if (!validation.ok) {
|
|
18947
19796
|
return mcpJson({ present: true, valid: false, error: validation.error.message });
|
|
@@ -18949,10 +19798,10 @@ async function handleReadStrategy(input) {
|
|
|
18949
19798
|
if (!validation.value.present) {
|
|
18950
19799
|
return mcpJson({ present: false, valid: true });
|
|
18951
19800
|
}
|
|
18952
|
-
const strategyPath =
|
|
19801
|
+
const strategyPath = path54.join(projectPath, STRATEGY_FILENAME);
|
|
18953
19802
|
let raw;
|
|
18954
19803
|
try {
|
|
18955
|
-
raw = await
|
|
19804
|
+
raw = await fs52.readFile(strategyPath, "utf-8");
|
|
18956
19805
|
} catch (error) {
|
|
18957
19806
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18958
19807
|
}
|
|
@@ -18993,7 +19842,7 @@ async function handleWriteStrategy(input) {
|
|
|
18993
19842
|
} catch (error) {
|
|
18994
19843
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18995
19844
|
}
|
|
18996
|
-
const core = await import("./dist-
|
|
19845
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
18997
19846
|
try {
|
|
18998
19847
|
core.writeStrategyDoc(input.doc, {
|
|
18999
19848
|
cwd: projectPath,
|
|
@@ -19008,11 +19857,11 @@ async function handleWriteStrategy(input) {
|
|
|
19008
19857
|
true
|
|
19009
19858
|
);
|
|
19010
19859
|
}
|
|
19011
|
-
return mcpJson({ written: true, path:
|
|
19860
|
+
return mcpJson({ written: true, path: path54.join(projectPath, STRATEGY_FILENAME) });
|
|
19012
19861
|
}
|
|
19013
19862
|
|
|
19014
19863
|
// src/mcp/tools/pulse.ts
|
|
19015
|
-
import * as
|
|
19864
|
+
import * as path55 from "path";
|
|
19016
19865
|
function mcpJson2(payload, isError = false) {
|
|
19017
19866
|
return { content: [{ type: "text", text: JSON.stringify(payload) }], isError };
|
|
19018
19867
|
}
|
|
@@ -19037,7 +19886,7 @@ async function handleSeedPulseFromStrategy(input) {
|
|
|
19037
19886
|
} catch (error) {
|
|
19038
19887
|
return mcpError3(error instanceof Error ? error.message : String(error));
|
|
19039
19888
|
}
|
|
19040
|
-
const core = await import("./dist-
|
|
19889
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
19041
19890
|
const seed = core.seedFromStrategy({ cwd: projectPath });
|
|
19042
19891
|
return mcpJson2(seed);
|
|
19043
19892
|
}
|
|
@@ -19071,8 +19920,8 @@ async function handleWritePulseConfig(input) {
|
|
|
19071
19920
|
} catch (error) {
|
|
19072
19921
|
return mcpError3(error instanceof Error ? error.message : String(error));
|
|
19073
19922
|
}
|
|
19074
|
-
const resolvedConfigPath = input.configPath !== void 0 ?
|
|
19075
|
-
const core = await import("./dist-
|
|
19923
|
+
const resolvedConfigPath = input.configPath !== void 0 ? path55.isAbsolute(input.configPath) ? input.configPath : path55.join(projectPath, input.configPath) : path55.join(projectPath, "harness.config.json");
|
|
19924
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
19076
19925
|
try {
|
|
19077
19926
|
core.writePulseConfig(input.config, {
|
|
19078
19927
|
configPath: resolvedConfigPath,
|
|
@@ -19121,7 +19970,7 @@ async function handleAcquireCompoundLock(input) {
|
|
|
19121
19970
|
} catch (error) {
|
|
19122
19971
|
return mcpError4(error instanceof Error ? error.message : String(error));
|
|
19123
19972
|
}
|
|
19124
|
-
const core = await import("./dist-
|
|
19973
|
+
const core = await import("./dist-6D2F2J57.js");
|
|
19125
19974
|
try {
|
|
19126
19975
|
const handle = core.acquireCompoundLock(
|
|
19127
19976
|
input.category,
|
|
@@ -19282,6 +20131,7 @@ var TOOL_DEFINITIONS = [
|
|
|
19282
20131
|
knowledgeCraftDefinition,
|
|
19283
20132
|
securityCraftDefinition,
|
|
19284
20133
|
outcomeEvalDefinition,
|
|
20134
|
+
acceptanceEvalDefinition,
|
|
19285
20135
|
validateStrategyDefinition,
|
|
19286
20136
|
readStrategyDefinition,
|
|
19287
20137
|
writeStrategyDefinition,
|
|
@@ -19375,6 +20225,7 @@ var TOOL_HANDLERS = {
|
|
|
19375
20225
|
knowledge_craft: handleKnowledgeCraft,
|
|
19376
20226
|
security_craft: handleSecurityCraft,
|
|
19377
20227
|
outcome_eval: handleOutcomeEval,
|
|
20228
|
+
acceptance_eval: handleAcceptanceEval,
|
|
19378
20229
|
validate_strategy: handleValidateStrategy,
|
|
19379
20230
|
read_strategy: handleReadStrategy,
|
|
19380
20231
|
write_strategy: handleWriteStrategy,
|
|
@@ -19483,7 +20334,7 @@ async function appendUpdateNotification(result, resolvedRoot) {
|
|
|
19483
20334
|
shouldRunCheck,
|
|
19484
20335
|
readCheckState,
|
|
19485
20336
|
spawnBackgroundCheck
|
|
19486
|
-
} = await import("./dist-
|
|
20337
|
+
} = await import("./dist-6D2F2J57.js");
|
|
19487
20338
|
const { CLI_VERSION } = await import("./version-CUI434OP.js");
|
|
19488
20339
|
const configInterval = readConfigInterval(resolvedRoot);
|
|
19489
20340
|
const DEFAULT_INTERVAL = 864e5;
|