@shanepadgett/tau-agent 0.33.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/bash-approval/README.md +20 -0
- package/extensions/bash-approval/index.ts +235 -0
- package/extensions/bash-approval/settings.ts +24 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +66 -0
- package/extensions/checkpoint/checkpoint.ts +279 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +170 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/commit/commit-plan.ts +2 -2
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/index.ts +2 -0
- package/extensions/reference/panel.ts +94 -31
- package/extensions/review/README.md +14 -6
- package/extensions/review/index.ts +58 -99
- package/extensions/review/model.ts +13 -33
- package/extensions/review/session.ts +9 -23
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/soul/README.md +2 -2
- package/extensions/soul/index.ts +4 -4
- package/extensions/soul/prompt.ts +6 -10
- package/extensions/soul/settings.ts +6 -3
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents/web-research.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +308 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +20 -8
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +38 -35
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +72 -30
- package/shared/model-effort.ts +23 -22
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/review/panel.ts +0 -128
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { exists, resolveProjectRoot } from "../../shared/settings/paths.ts";
|
|
4
|
+
import { type ReadyReport, type ReadyRow, countStatuses, localTimestampLabel, row } from "./model.ts";
|
|
5
|
+
import { CAPABILITY_LABELS, CAPABILITY_ORDER, type LanguagePack, LANGUAGE_PACKS, type PackTool } from "./packs.ts";
|
|
6
|
+
|
|
7
|
+
const POLICY_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md", ".github/copilot-instructions.md", "AGENT.md"];
|
|
8
|
+
const VOCAB_FILES = ["docs/VOCABULARY.md", "VOCABULARY.md", "docs/vocabulary.md"];
|
|
9
|
+
const COLD_START_DOCS = [
|
|
10
|
+
"docs/CONTRIBUTING.md",
|
|
11
|
+
"CONTRIBUTING.md",
|
|
12
|
+
"docs/HACKING.md",
|
|
13
|
+
"HACKING.md",
|
|
14
|
+
"docs/DEVELOPMENT.md",
|
|
15
|
+
"DEVELOPMENT.md",
|
|
16
|
+
];
|
|
17
|
+
const TOOLCHAIN_FILES = [
|
|
18
|
+
"mise.toml",
|
|
19
|
+
".mise.toml",
|
|
20
|
+
"devbox.json",
|
|
21
|
+
".tool-versions",
|
|
22
|
+
"flake.nix",
|
|
23
|
+
".devcontainer/devcontainer.json",
|
|
24
|
+
"devcontainer.json",
|
|
25
|
+
];
|
|
26
|
+
const TASK_RUNNER_FILES = [
|
|
27
|
+
"mise.toml",
|
|
28
|
+
".mise.toml",
|
|
29
|
+
"justfile",
|
|
30
|
+
"Justfile",
|
|
31
|
+
"Makefile",
|
|
32
|
+
"Taskfile.yml",
|
|
33
|
+
"Taskfile.yaml",
|
|
34
|
+
];
|
|
35
|
+
const STANDARDS_DIRS = ["docs/standards", ".pi/standards", "standards"];
|
|
36
|
+
const BOOTSTRAP_SCRIPTS = ["scripts/bootstrap.sh", "scripts/setup.sh", "bin/setup", "bootstrap.sh", "setup.sh"];
|
|
37
|
+
const AGENTS_WEAK_BYTES = 24_000;
|
|
38
|
+
const AGENTS_WEAK_LINES = 400;
|
|
39
|
+
|
|
40
|
+
interface FsIndex {
|
|
41
|
+
root: string;
|
|
42
|
+
files: Set<string>;
|
|
43
|
+
textBlobs: string[];
|
|
44
|
+
packageScripts: string;
|
|
45
|
+
miseText: string;
|
|
46
|
+
ciText: string;
|
|
47
|
+
tauSettingsText: string;
|
|
48
|
+
fallowText: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function scanReadyReport(cwd: string): Promise<ReadyReport> {
|
|
52
|
+
const root = await resolveProjectRoot(cwd);
|
|
53
|
+
const fs = await buildFsIndex(root);
|
|
54
|
+
const languages = detectLanguages(fs);
|
|
55
|
+
const packs = LANGUAGE_PACKS.filter((pack) => languages.includes(pack.id));
|
|
56
|
+
const rows: ReadyRow[] = [...(await scanGeneral(fs)), ...scanLintEntropy(fs, packs, languages)];
|
|
57
|
+
const generatedAt = new Date();
|
|
58
|
+
return {
|
|
59
|
+
generatedAt: generatedAt.toISOString(),
|
|
60
|
+
generatedAtLabel: localTimestampLabel(generatedAt),
|
|
61
|
+
root,
|
|
62
|
+
languages,
|
|
63
|
+
rows,
|
|
64
|
+
counts: countStatuses(rows),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function buildFsIndex(root: string): Promise<FsIndex> {
|
|
69
|
+
const files = new Set<string>();
|
|
70
|
+
const probe = [
|
|
71
|
+
...POLICY_FILES,
|
|
72
|
+
...VOCAB_FILES,
|
|
73
|
+
...COLD_START_DOCS,
|
|
74
|
+
...TOOLCHAIN_FILES,
|
|
75
|
+
...TASK_RUNNER_FILES,
|
|
76
|
+
...BOOTSTRAP_SCRIPTS,
|
|
77
|
+
"README.md",
|
|
78
|
+
"package.json",
|
|
79
|
+
"package-lock.json",
|
|
80
|
+
"pnpm-lock.yaml",
|
|
81
|
+
"yarn.lock",
|
|
82
|
+
"bun.lock",
|
|
83
|
+
"deno.json",
|
|
84
|
+
"deno.jsonc",
|
|
85
|
+
"tsconfig.json",
|
|
86
|
+
"tsconfig.base.json",
|
|
87
|
+
"jsconfig.json",
|
|
88
|
+
"go.mod",
|
|
89
|
+
"Cargo.toml",
|
|
90
|
+
"rustfmt.toml",
|
|
91
|
+
".rustfmt.toml",
|
|
92
|
+
"clippy.toml",
|
|
93
|
+
".clippy.toml",
|
|
94
|
+
"deny.toml",
|
|
95
|
+
".golangci.yml",
|
|
96
|
+
".golangci.yaml",
|
|
97
|
+
".golangci.toml",
|
|
98
|
+
".golangci.json",
|
|
99
|
+
".oxfmtrc.jsonc",
|
|
100
|
+
".oxfmtrc.json",
|
|
101
|
+
".oxlintrc.jsonc",
|
|
102
|
+
".oxlintrc.json",
|
|
103
|
+
"oxlint.config.ts",
|
|
104
|
+
"biome.json",
|
|
105
|
+
"biome.jsonc",
|
|
106
|
+
"dprint.json",
|
|
107
|
+
"dprint.jsonc",
|
|
108
|
+
".fallowrc.json",
|
|
109
|
+
".fallowrc.jsonc",
|
|
110
|
+
"fallow.toml",
|
|
111
|
+
".fallow.toml",
|
|
112
|
+
"knip.json",
|
|
113
|
+
"knip.jsonc",
|
|
114
|
+
".knip.json",
|
|
115
|
+
"knip.ts",
|
|
116
|
+
"knip.config.ts",
|
|
117
|
+
".jscpd.json",
|
|
118
|
+
".dependency-cruiser.js",
|
|
119
|
+
".dependency-cruiser.cjs",
|
|
120
|
+
".dependency-cruiser.mjs",
|
|
121
|
+
".dependency-cruiser.json",
|
|
122
|
+
".prettierrc",
|
|
123
|
+
".prettierrc.json",
|
|
124
|
+
".prettierrc.js",
|
|
125
|
+
".prettierrc.cjs",
|
|
126
|
+
"prettier.config.js",
|
|
127
|
+
"prettier.config.cjs",
|
|
128
|
+
"prettier.config.mjs",
|
|
129
|
+
"eslint.config.js",
|
|
130
|
+
"eslint.config.mjs",
|
|
131
|
+
"eslint.config.cjs",
|
|
132
|
+
"eslint.config.ts",
|
|
133
|
+
".eslintrc",
|
|
134
|
+
".eslintrc.js",
|
|
135
|
+
".eslintrc.cjs",
|
|
136
|
+
".eslintrc.json",
|
|
137
|
+
".eslintrc.yml",
|
|
138
|
+
".pi/tau/settings.json",
|
|
139
|
+
".pi/contexts",
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
await Promise.all(
|
|
143
|
+
probe.map(async (rel) => {
|
|
144
|
+
if (await exists(join(root, rel))) files.add(rel);
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
for (const dir of [".github/workflows", ".pi/extensions", "docs/standards", ".pi/standards", "standards"]) {
|
|
149
|
+
if (await exists(join(root, dir))) files.add(dir);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const workflowNames = await listNames(join(root, ".github/workflows"));
|
|
153
|
+
for (const name of workflowNames) files.add(`.github/workflows/${name}`);
|
|
154
|
+
const extensionNames = await listNames(join(root, ".pi/extensions"));
|
|
155
|
+
for (const name of extensionNames) files.add(`.pi/extensions/${name}`);
|
|
156
|
+
const contextTop = await listNames(join(root, ".pi/contexts"));
|
|
157
|
+
for (const name of contextTop) files.add(`.pi/contexts/${name}`);
|
|
158
|
+
|
|
159
|
+
const packageScripts = await readPackageScriptBlob(root);
|
|
160
|
+
const miseText =
|
|
161
|
+
(await readIfExists(join(root, "mise.toml"))) ?? (await readIfExists(join(root, ".mise.toml"))) ?? "";
|
|
162
|
+
const ciText = await readCiBlob(root, workflowNames);
|
|
163
|
+
const tauSettingsText = (await readIfExists(join(root, ".pi/tau/settings.json"))) ?? "";
|
|
164
|
+
const fallowText =
|
|
165
|
+
(await readIfExists(join(root, ".fallowrc.jsonc"))) ??
|
|
166
|
+
(await readIfExists(join(root, ".fallowrc.json"))) ??
|
|
167
|
+
(await readIfExists(join(root, "fallow.toml"))) ??
|
|
168
|
+
(await readIfExists(join(root, ".fallow.toml"))) ??
|
|
169
|
+
"";
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
root,
|
|
173
|
+
files,
|
|
174
|
+
textBlobs: [packageScripts, miseText, ciText, tauSettingsText, fallowText],
|
|
175
|
+
packageScripts,
|
|
176
|
+
miseText,
|
|
177
|
+
ciText,
|
|
178
|
+
tauSettingsText,
|
|
179
|
+
fallowText,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function detectLanguages(fs: FsIndex): string[] {
|
|
184
|
+
const ids: string[] = [];
|
|
185
|
+
const has = (rel: string) => fs.files.has(rel);
|
|
186
|
+
if (has("deno.json") || has("deno.jsonc")) ids.push("deno");
|
|
187
|
+
if (has("package.json") || has("tsconfig.json") || has("tsconfig.base.json") || has("jsconfig.json")) {
|
|
188
|
+
ids.push("typescript");
|
|
189
|
+
}
|
|
190
|
+
if (has("go.mod")) ids.push("go");
|
|
191
|
+
if (has("Cargo.toml")) ids.push("rust");
|
|
192
|
+
return ids;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function presentPaths(fs: FsIndex, rels: readonly string[]): string[] {
|
|
196
|
+
return rels.filter((rel) => fs.files.has(rel));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function scanGeneral(fs: FsIndex): Promise<ReadyRow[]> {
|
|
200
|
+
const npmScripts = extractNpmScriptNames(fs.packageScripts);
|
|
201
|
+
const miseTasks = extractMiseTaskNames(fs.miseText);
|
|
202
|
+
const policy = presentPaths(fs, POLICY_FILES);
|
|
203
|
+
return [
|
|
204
|
+
...scanColdStart(fs),
|
|
205
|
+
...scanToolchain(fs, npmScripts),
|
|
206
|
+
...scanVerify(fs, miseTasks, npmScripts),
|
|
207
|
+
...(await scanPolicy(fs, policy)),
|
|
208
|
+
...scanStandards(fs),
|
|
209
|
+
...scanContextCatalog(fs),
|
|
210
|
+
...(await scanMarkers(fs, policy)),
|
|
211
|
+
scanHarnessGates(),
|
|
212
|
+
scanSideEffectVerbs(fs, npmScripts),
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function scanColdStart(fs: FsIndex): ReadyRow[] {
|
|
217
|
+
const coldDocs = presentPaths(fs, COLD_START_DOCS);
|
|
218
|
+
const bootstraps = presentPaths(fs, BOOTSTRAP_SCRIPTS);
|
|
219
|
+
const rows: ReadyRow[] = [];
|
|
220
|
+
if (coldDocs.length > 0) {
|
|
221
|
+
rows.push(
|
|
222
|
+
row({
|
|
223
|
+
id: "cold-start.doc",
|
|
224
|
+
area: "cold-start",
|
|
225
|
+
title: "Cold-start documentation",
|
|
226
|
+
status: "pass",
|
|
227
|
+
evidence: coldDocs,
|
|
228
|
+
note: "Contributor/dev bootstrap doc present (content quality not judged in scan).",
|
|
229
|
+
next: bootstraps.length === 0 ? "Optional: add a bootstrap script that matches the doc." : undefined,
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
} else if (fs.files.has("README.md")) {
|
|
233
|
+
rows.push(
|
|
234
|
+
row({
|
|
235
|
+
id: "cold-start.doc",
|
|
236
|
+
area: "cold-start",
|
|
237
|
+
title: "Cold-start documentation",
|
|
238
|
+
status: "weak",
|
|
239
|
+
evidence: ["README.md"],
|
|
240
|
+
note: "Only README found. Prefer a dedicated CONTRIBUTING/HACKING cold-start path.",
|
|
241
|
+
next: "Add docs/CONTRIBUTING.md (or similar) with ordered clean-machine steps ending in verify.",
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
} else {
|
|
245
|
+
rows.push(
|
|
246
|
+
row({
|
|
247
|
+
id: "cold-start.doc",
|
|
248
|
+
area: "cold-start",
|
|
249
|
+
title: "Cold-start documentation",
|
|
250
|
+
status: "missing",
|
|
251
|
+
note: "No CONTRIBUTING/HACKING/DEVELOPMENT doc or README.",
|
|
252
|
+
next: "Document ordered install → verify from a clean machine.",
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (bootstraps.length > 0) {
|
|
257
|
+
rows.push(
|
|
258
|
+
row({
|
|
259
|
+
id: "cold-start.script",
|
|
260
|
+
area: "cold-start",
|
|
261
|
+
title: "Bootstrap script",
|
|
262
|
+
status: "pass",
|
|
263
|
+
evidence: bootstraps,
|
|
264
|
+
note: "Bootstrap/setup script path present.",
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return rows;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function scanToolchain(fs: FsIndex, npmScripts: string[]): ReadyRow[] {
|
|
272
|
+
const toolchain = presentPaths(fs, TOOLCHAIN_FILES);
|
|
273
|
+
const taskFiles = presentPaths(fs, TASK_RUNNER_FILES);
|
|
274
|
+
const pin =
|
|
275
|
+
toolchain.length > 0
|
|
276
|
+
? row({
|
|
277
|
+
id: "toolchain.pin",
|
|
278
|
+
area: "toolchain",
|
|
279
|
+
title: "Pinned toolchain",
|
|
280
|
+
status: "pass",
|
|
281
|
+
evidence: toolchain,
|
|
282
|
+
note: "Toolchain pin or devcontainer-style bootstrap config found.",
|
|
283
|
+
})
|
|
284
|
+
: row({
|
|
285
|
+
id: "toolchain.pin",
|
|
286
|
+
area: "toolchain",
|
|
287
|
+
title: "Pinned toolchain",
|
|
288
|
+
status: "missing",
|
|
289
|
+
note: "No mise/devbox/asdf/nix/devcontainer pin detected.",
|
|
290
|
+
next: "Add a reproducible toolchain pin (mise.toml is a common choice).",
|
|
291
|
+
});
|
|
292
|
+
const tasks =
|
|
293
|
+
taskFiles.length > 0 || npmScripts.length > 0
|
|
294
|
+
? row({
|
|
295
|
+
id: "toolchain.tasks",
|
|
296
|
+
area: "toolchain",
|
|
297
|
+
title: "Task runner",
|
|
298
|
+
status: "pass",
|
|
299
|
+
evidence: [...taskFiles, ...npmScripts.slice(0, 12).map((name) => `npm:${name}`)],
|
|
300
|
+
note: "Named tasks or package scripts found.",
|
|
301
|
+
})
|
|
302
|
+
: row({
|
|
303
|
+
id: "toolchain.tasks",
|
|
304
|
+
area: "toolchain",
|
|
305
|
+
title: "Task runner",
|
|
306
|
+
status: "missing",
|
|
307
|
+
note: "No mise/just/make/taskfile/npm scripts detected.",
|
|
308
|
+
next: "Add a task runner with a single verify entrypoint.",
|
|
309
|
+
});
|
|
310
|
+
return [pin, tasks];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function scanVerify(fs: FsIndex, miseTasks: string[], npmScripts: string[]): ReadyRow[] {
|
|
314
|
+
return [...scanVerifyEntry(fs, miseTasks, npmScripts), scanSilentRunner(fs)];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function scanVerifyEntry(fs: FsIndex, miseTasks: string[], npmScripts: string[]): ReadyRow[] {
|
|
318
|
+
const verifyNames = findVerifyNames(miseTasks, npmScripts, fs);
|
|
319
|
+
if (verifyNames.length === 0) {
|
|
320
|
+
return [
|
|
321
|
+
row({
|
|
322
|
+
id: "verify.entry",
|
|
323
|
+
area: "verify",
|
|
324
|
+
title: "Verify entrypoint",
|
|
325
|
+
status: "missing",
|
|
326
|
+
note: "No check/verify/ci-style task name detected.",
|
|
327
|
+
next: "Add one verify task (e.g. mise run check) that aggregates format/lint/types/entropy.",
|
|
328
|
+
}),
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
const rows: ReadyRow[] = [
|
|
332
|
+
row({
|
|
333
|
+
id: "verify.entry",
|
|
334
|
+
area: "verify",
|
|
335
|
+
title: "Verify entrypoint",
|
|
336
|
+
status: "pass",
|
|
337
|
+
evidence: verifyNames,
|
|
338
|
+
note: "Conventional verify/check task or script found.",
|
|
339
|
+
}),
|
|
340
|
+
];
|
|
341
|
+
if (!(fs.files.has(".github/workflows") || fs.ciText.length > 0)) return rows;
|
|
342
|
+
const ciHits = verifyNames.filter(
|
|
343
|
+
(name) => textIncludes(fs.ciText, name) || textIncludes(fs.ciText, `mise run ${name.replace(/^mise:/, "")}`),
|
|
344
|
+
);
|
|
345
|
+
rows.push(
|
|
346
|
+
row({
|
|
347
|
+
id: "verify.ci",
|
|
348
|
+
area: "verify",
|
|
349
|
+
title: "CI parity (heuristic)",
|
|
350
|
+
status: ciHits.length > 0 ? "pass" : "weak",
|
|
351
|
+
evidence: ciHits.length > 0 ? ciHits.map((name) => `ci mentions ${name}`) : [".github/workflows present"],
|
|
352
|
+
note:
|
|
353
|
+
ciHits.length > 0
|
|
354
|
+
? "CI text appears to reference a verify task."
|
|
355
|
+
: "Workflows present but verify task name not obviously referenced.",
|
|
356
|
+
next: ciHits.length > 0 ? undefined : "Make CI run the same verify entry humans and agents use.",
|
|
357
|
+
}),
|
|
358
|
+
);
|
|
359
|
+
return rows;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function scanSilentRunner(fs: FsIndex): ReadyRow {
|
|
363
|
+
const hasSection = fs.files.has(".pi/tau/settings.json") && /"silentCommandRunner"\s*:/.test(fs.tauSettingsText);
|
|
364
|
+
if (!hasSection) {
|
|
365
|
+
return row({
|
|
366
|
+
id: "verify.silent",
|
|
367
|
+
area: "verify",
|
|
368
|
+
title: "Silent command runner",
|
|
369
|
+
status: "missing",
|
|
370
|
+
note: "No Tau silentCommandRunner configuration detected.",
|
|
371
|
+
next: "Wire automatic verify after edits via extensions.silentCommandRunner.",
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const silentConfigured =
|
|
375
|
+
/"silentCommandRunner"\s*:/.test(fs.tauSettingsText) &&
|
|
376
|
+
/"commands"\s*:\s*\[/.test(fs.tauSettingsText) &&
|
|
377
|
+
!/"commands"\s*:\s*\[\s*\]/.test(fs.tauSettingsText);
|
|
378
|
+
return row({
|
|
379
|
+
id: "verify.silent",
|
|
380
|
+
area: "verify",
|
|
381
|
+
title: "Silent command runner",
|
|
382
|
+
status: silentConfigured ? "pass" : "weak",
|
|
383
|
+
evidence: [".pi/tau/settings.json"],
|
|
384
|
+
note: silentConfigured
|
|
385
|
+
? "silentCommandRunner has configured commands."
|
|
386
|
+
: "silentCommandRunner section present but commands look empty.",
|
|
387
|
+
next: silentConfigured ? undefined : "Configure extensions.silentCommandRunner.commands for post-edit verify.",
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function scanPolicy(fs: FsIndex, policy: string[]): Promise<ReadyRow[]> {
|
|
392
|
+
return [await scanAgentsPolicy(fs, policy), scanVocabulary(fs)];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function scanAgentsPolicy(fs: FsIndex, policy: string[]): Promise<ReadyRow> {
|
|
396
|
+
if (policy.length === 0) {
|
|
397
|
+
return row({
|
|
398
|
+
id: "policy.agents",
|
|
399
|
+
area: "policy",
|
|
400
|
+
title: "Agent policy file",
|
|
401
|
+
status: "missing",
|
|
402
|
+
note: "No AGENTS.md / CLAUDE.md / copilot-instructions file found.",
|
|
403
|
+
next: "Add a thin AGENTS.md with always-on rules and pointers outward.",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const primary = policy[0];
|
|
407
|
+
const text = primary ? await readIfExists(join(fs.root, primary)) : undefined;
|
|
408
|
+
const bytes = text === undefined ? 0 : Buffer.byteLength(text, "utf8");
|
|
409
|
+
const lines = text === undefined ? 0 : text.split(/\r?\n/).length;
|
|
410
|
+
const weakSize = text !== undefined && (bytes > AGENTS_WEAK_BYTES || lines > AGENTS_WEAK_LINES);
|
|
411
|
+
return row({
|
|
412
|
+
id: "policy.agents",
|
|
413
|
+
area: "policy",
|
|
414
|
+
title: "Agent policy file",
|
|
415
|
+
status: weakSize ? "weak" : "pass",
|
|
416
|
+
evidence: [...policy, ...(text !== undefined && primary ? [`${primary}: ${lines} lines, ${bytes} bytes`] : [])],
|
|
417
|
+
note: weakSize
|
|
418
|
+
? "Policy file is large; prefer thin always-on law and move playbooks to standards."
|
|
419
|
+
: "Always-on agent policy file present (thinness of content not fully judged).",
|
|
420
|
+
next: weakSize ? "Split work-type standards out of AGENTS.md; keep durable cross-cutting rules only." : undefined,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function scanVocabulary(fs: FsIndex): ReadyRow {
|
|
425
|
+
const vocab = presentPaths(fs, VOCAB_FILES);
|
|
426
|
+
if (vocab.length > 0) {
|
|
427
|
+
return row({
|
|
428
|
+
id: "policy.vocabulary",
|
|
429
|
+
area: "policy",
|
|
430
|
+
title: "Vocabulary",
|
|
431
|
+
status: "pass",
|
|
432
|
+
evidence: vocab,
|
|
433
|
+
note: "Shared vocabulary doc found.",
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return row({
|
|
437
|
+
id: "policy.vocabulary",
|
|
438
|
+
area: "policy",
|
|
439
|
+
title: "Vocabulary",
|
|
440
|
+
status: "missing",
|
|
441
|
+
note: "No VOCABULARY.md detected.",
|
|
442
|
+
next: "Add docs/VOCABULARY.md for core nouns agents and humans share.",
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function scanStandards(fs: FsIndex): ReadyRow[] {
|
|
447
|
+
const standardsHits = STANDARDS_DIRS.filter((dir) => fs.files.has(dir));
|
|
448
|
+
return [
|
|
449
|
+
standardsHits.length > 0
|
|
450
|
+
? row({
|
|
451
|
+
id: "standards.tree",
|
|
452
|
+
area: "standards",
|
|
453
|
+
title: "Standards tree",
|
|
454
|
+
status: "pass",
|
|
455
|
+
evidence: standardsHits,
|
|
456
|
+
note: "Standards directory present (substance not judged in scan).",
|
|
457
|
+
})
|
|
458
|
+
: row({
|
|
459
|
+
id: "standards.tree",
|
|
460
|
+
area: "standards",
|
|
461
|
+
title: "Standards tree",
|
|
462
|
+
status: "missing",
|
|
463
|
+
note: "No docs/standards or .pi/standards tree detected.",
|
|
464
|
+
next: "Add work-type standards outside AGENTS.md (UI, API, extensions, …).",
|
|
465
|
+
}),
|
|
466
|
+
];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function scanContextCatalog(fs: FsIndex): ReadyRow[] {
|
|
470
|
+
if (fs.files.has(".pi/contexts")) {
|
|
471
|
+
const children = [...fs.files].filter((path) => path.startsWith(".pi/contexts/") && path !== ".pi/contexts");
|
|
472
|
+
return [
|
|
473
|
+
row({
|
|
474
|
+
id: "context.catalog",
|
|
475
|
+
area: "context",
|
|
476
|
+
title: "Context catalog",
|
|
477
|
+
status: children.length > 0 ? "pass" : "weak",
|
|
478
|
+
evidence: children.length > 0 ? children.slice(0, 12) : [".pi/contexts"],
|
|
479
|
+
note:
|
|
480
|
+
children.length > 0
|
|
481
|
+
? "`.pi/contexts` has entries (pack quality not judged in scan)."
|
|
482
|
+
: "`.pi/contexts` exists but looks empty at top level.",
|
|
483
|
+
next: children.length > 0 ? undefined : "Add domain/concept work packs under .pi/contexts.",
|
|
484
|
+
}),
|
|
485
|
+
];
|
|
486
|
+
}
|
|
487
|
+
return [
|
|
488
|
+
row({
|
|
489
|
+
id: "context.catalog",
|
|
490
|
+
area: "context",
|
|
491
|
+
title: "Context catalog",
|
|
492
|
+
status: "missing",
|
|
493
|
+
note: "No `.pi/contexts` catalog detected.",
|
|
494
|
+
next: "Create job-shaped context packs for sticky work areas.",
|
|
495
|
+
}),
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function scanMarkers(fs: FsIndex, policy: string[]): Promise<ReadyRow[]> {
|
|
500
|
+
const policyBlob = policy[0] ? ((await readIfExists(join(fs.root, policy[0]))) ?? "") : "";
|
|
501
|
+
const markerHit =
|
|
502
|
+
textIncludes(fs.fallowText, "@agent") ||
|
|
503
|
+
textIncludes(fs.tauSettingsText, "@agent kind") ||
|
|
504
|
+
textIncludes(policyBlob, "@agent kind=");
|
|
505
|
+
return [
|
|
506
|
+
row({
|
|
507
|
+
id: "markers.vocab",
|
|
508
|
+
area: "markers",
|
|
509
|
+
title: "Deterministic markers",
|
|
510
|
+
status: markerHit ? "weak" : "na",
|
|
511
|
+
evidence: markerHit ? ["heuristic marker mention"] : [],
|
|
512
|
+
note: markerHit
|
|
513
|
+
? "Marker-related signal found; full greppable vocabulary not verified."
|
|
514
|
+
: "No marker system required; na unless the repo adopts @agent-style temp/until/invariant tags.",
|
|
515
|
+
}),
|
|
516
|
+
];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function scanHarnessGates(): ReadyRow {
|
|
520
|
+
return row({
|
|
521
|
+
id: "harness.gates",
|
|
522
|
+
area: "harness",
|
|
523
|
+
title: "Harness gates",
|
|
524
|
+
status: "unknown",
|
|
525
|
+
note: "Scan does not verify read/write/command gates in v1.",
|
|
526
|
+
next: "Protect rail files and secrets via harness permissions when available.",
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function scanSideEffectVerbs(fs: FsIndex, npmScripts: string[]): ReadyRow {
|
|
531
|
+
const localExt = [...fs.files].filter((path) => path.startsWith(".pi/extensions/"));
|
|
532
|
+
const publishExt = localExt.filter((path) => /publish|release|migrate/i.test(path));
|
|
533
|
+
const publishScripts = npmScripts.filter((name) => /publish|release|migrate/i.test(name));
|
|
534
|
+
if (publishExt.length > 0 || publishScripts.length > 0) {
|
|
535
|
+
return row({
|
|
536
|
+
id: "side-effect.verbs",
|
|
537
|
+
area: "side-effect-verbs",
|
|
538
|
+
title: "Side-effect verbs",
|
|
539
|
+
status: "pass",
|
|
540
|
+
evidence: [...publishExt, ...publishScripts.map((name) => `npm:${name}`)],
|
|
541
|
+
note: "Publish/release/migrate-style command or local extension name found.",
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
return row({
|
|
545
|
+
id: "side-effect.verbs",
|
|
546
|
+
area: "side-effect-verbs",
|
|
547
|
+
title: "Side-effect verbs",
|
|
548
|
+
status: "na",
|
|
549
|
+
note: "No publish/release/migrate verb detected; fine if the repo has no such ops.",
|
|
550
|
+
next: "Bind side-effectful ops to slash commands or scripts, not agent freestyle.",
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function scanLintEntropy(fs: FsIndex, packs: LanguagePack[], languages: string[]): ReadyRow[] {
|
|
555
|
+
if (languages.length === 0) {
|
|
556
|
+
return [
|
|
557
|
+
row({
|
|
558
|
+
id: "lint-entropy.languages",
|
|
559
|
+
area: "lint-entropy",
|
|
560
|
+
title: "Language detection",
|
|
561
|
+
status: "unknown",
|
|
562
|
+
note: "No supported language manifests detected (TS/JS, Deno, Go, Rust).",
|
|
563
|
+
}),
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const rows: ReadyRow[] = [
|
|
568
|
+
row({
|
|
569
|
+
id: "lint-entropy.languages",
|
|
570
|
+
area: "lint-entropy",
|
|
571
|
+
title: "Detected languages",
|
|
572
|
+
status: "pass",
|
|
573
|
+
evidence: languages,
|
|
574
|
+
note: `Active packs: ${packs.map((pack) => pack.label).join(", ")}.`,
|
|
575
|
+
}),
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
for (const pack of packs) {
|
|
579
|
+
for (const capability of CAPABILITY_ORDER) {
|
|
580
|
+
const scored = scoreLintCapability(fs, pack, capability);
|
|
581
|
+
if (scored) rows.push(scored);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return rows;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function scoreLintCapability(
|
|
589
|
+
fs: FsIndex,
|
|
590
|
+
pack: LanguagePack,
|
|
591
|
+
capability: (typeof CAPABILITY_ORDER)[number],
|
|
592
|
+
): ReadyRow | undefined {
|
|
593
|
+
const spec = pack.capabilities[capability];
|
|
594
|
+
if (!spec) return undefined;
|
|
595
|
+
const id = `lint-entropy.${pack.id}.${capability}`;
|
|
596
|
+
const title = `${pack.label}: ${CAPABILITY_LABELS[capability]}`;
|
|
597
|
+
if (spec.unsupportedReason && spec.tools.length === 0) {
|
|
598
|
+
return row({ id, area: "lint-entropy", title, status: "na", note: spec.unsupportedReason });
|
|
599
|
+
}
|
|
600
|
+
const hits = findToolHits(fs, spec.tools);
|
|
601
|
+
if (hits.length === 0) {
|
|
602
|
+
if (spec.unsupportedReason) {
|
|
603
|
+
return row({ id, area: "lint-entropy", title, status: "na", note: spec.unsupportedReason });
|
|
604
|
+
}
|
|
605
|
+
return row({
|
|
606
|
+
id,
|
|
607
|
+
area: "lint-entropy",
|
|
608
|
+
title,
|
|
609
|
+
status: "missing",
|
|
610
|
+
evidence: spec.tools.map((tool) => `looked for ${tool.name}`),
|
|
611
|
+
note: `No ${CAPABILITY_LABELS[capability].toLowerCase()} tool detected for ${pack.label}.`,
|
|
612
|
+
next: `Wire a ${pack.label} ${CAPABILITY_LABELS[capability].toLowerCase()} tool into verify.`,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
if (capability === "complexity" && hits.every((hit) => hit.startsWith("fallow")) && !fallowHealthWired(fs)) {
|
|
616
|
+
return row({
|
|
617
|
+
id,
|
|
618
|
+
area: "lint-entropy",
|
|
619
|
+
title,
|
|
620
|
+
status: "weak",
|
|
621
|
+
evidence: hits,
|
|
622
|
+
note: "Fallow present but health/complexity not obviously in verify tasks.",
|
|
623
|
+
next: "Add fallow health (or another complexity gate) to verify if you want complexity budgets.",
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (
|
|
627
|
+
capability === "boundaries" &&
|
|
628
|
+
hits.some((hit) => hit.startsWith("fallow")) &&
|
|
629
|
+
!textIncludes(fs.fallowText, "boundar") &&
|
|
630
|
+
!textIncludes(fs.miseText, "boundary")
|
|
631
|
+
) {
|
|
632
|
+
return row({
|
|
633
|
+
id,
|
|
634
|
+
area: "lint-entropy",
|
|
635
|
+
title,
|
|
636
|
+
status: "weak",
|
|
637
|
+
evidence: hits,
|
|
638
|
+
note: "Fallow present but boundary zones not detected in config/tasks.",
|
|
639
|
+
next: "Configure Fallow zones or dependency-cruiser if architecture boundaries matter.",
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
return row({
|
|
643
|
+
id,
|
|
644
|
+
area: "lint-entropy",
|
|
645
|
+
title,
|
|
646
|
+
status: "pass",
|
|
647
|
+
evidence: hits,
|
|
648
|
+
note: "Known tool configuration or task reference detected.",
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function findToolHits(fs: FsIndex, tools: readonly PackTool[]): string[] {
|
|
653
|
+
const hits: string[] = [];
|
|
654
|
+
for (const tool of tools) {
|
|
655
|
+
const fileHit = tool.detectFiles.find((rel) => fs.files.has(rel));
|
|
656
|
+
if (fileHit) {
|
|
657
|
+
hits.push(`${tool.name} (${fileHit})`);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const textHit = tool.detectText?.find((needle) => fs.textBlobs.some((blob) => textIncludes(blob, needle)));
|
|
661
|
+
if (textHit) hits.push(`${tool.name} (task/text: ${textHit})`);
|
|
662
|
+
}
|
|
663
|
+
return hits;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function fallowHealthWired(fs: FsIndex): boolean {
|
|
667
|
+
return (
|
|
668
|
+
/\bfallow\b[^\n]*\bhealth\b/i.test(fs.miseText) ||
|
|
669
|
+
/\bfallow\b[^\n]*\bhealth\b/i.test(fs.packageScripts) ||
|
|
670
|
+
textIncludes(fs.miseText, "fallow health") ||
|
|
671
|
+
textIncludes(fs.packageScripts, "fallow health")
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function findVerifyNames(miseTasks: string[], npmScripts: string[], fs: FsIndex): string[] {
|
|
676
|
+
const preferred = ["check", "verify", "ci", "lint", "test"];
|
|
677
|
+
const found = new Set<string>();
|
|
678
|
+
for (const name of miseTasks) {
|
|
679
|
+
if (preferred.some((part) => name === part || name.startsWith(`${part}:`) || name.endsWith(`:${part}`))) {
|
|
680
|
+
found.add(`mise:${name}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
for (const name of npmScripts) {
|
|
684
|
+
if (preferred.some((part) => name === part || name.startsWith(`${part}:`))) found.add(`npm:${name}`);
|
|
685
|
+
}
|
|
686
|
+
if (textIncludes(fs.miseText, "[tasks.check]") || textIncludes(fs.ciText, "mise run check")) found.add("mise:check");
|
|
687
|
+
return [...found];
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function extractMiseTaskNames(miseText: string): string[] {
|
|
691
|
+
const names: string[] = [];
|
|
692
|
+
for (const match of miseText.matchAll(/\[tasks\."([^"]+)"\]/g)) {
|
|
693
|
+
const raw = match[1];
|
|
694
|
+
if (raw) names.push(raw);
|
|
695
|
+
}
|
|
696
|
+
for (const match of miseText.matchAll(/\[tasks\.([A-Za-z0-9_-]+)\]/g)) {
|
|
697
|
+
const raw = match[1];
|
|
698
|
+
if (raw) names.push(raw);
|
|
699
|
+
}
|
|
700
|
+
return [...new Set(names)];
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function extractNpmScriptNames(packageJsonText: string): string[] {
|
|
704
|
+
if (!packageJsonText) return [];
|
|
705
|
+
try {
|
|
706
|
+
const parsed = JSON.parse(packageJsonText) as { scripts?: Record<string, string> };
|
|
707
|
+
return Object.keys(parsed.scripts ?? {});
|
|
708
|
+
} catch {
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async function readPackageScriptBlob(root: string): Promise<string> {
|
|
714
|
+
const text = await readIfExists(join(root, "package.json"));
|
|
715
|
+
if (!text) return "";
|
|
716
|
+
try {
|
|
717
|
+
const parsed = JSON.parse(text) as {
|
|
718
|
+
scripts?: Record<string, string>;
|
|
719
|
+
devDependencies?: Record<string, string>;
|
|
720
|
+
dependencies?: Record<string, string>;
|
|
721
|
+
};
|
|
722
|
+
return JSON.stringify({
|
|
723
|
+
scripts: parsed.scripts ?? {},
|
|
724
|
+
devDependencies: parsed.devDependencies ?? {},
|
|
725
|
+
dependencies: parsed.dependencies ?? {},
|
|
726
|
+
});
|
|
727
|
+
} catch {
|
|
728
|
+
return text;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async function readCiBlob(root: string, workflowNames: string[]): Promise<string> {
|
|
733
|
+
const chunks: string[] = [];
|
|
734
|
+
for (const name of workflowNames.slice(0, 20)) {
|
|
735
|
+
const text = await readIfExists(join(root, ".github/workflows", name));
|
|
736
|
+
if (text) chunks.push(text);
|
|
737
|
+
}
|
|
738
|
+
return chunks.join("\n");
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function listNames(dir: string): Promise<string[]> {
|
|
742
|
+
try {
|
|
743
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
744
|
+
return entries.map((entry) => entry.name);
|
|
745
|
+
} catch {
|
|
746
|
+
return [];
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function readIfExists(path: string): Promise<string | undefined> {
|
|
751
|
+
try {
|
|
752
|
+
return await readFile(path, "utf8");
|
|
753
|
+
} catch {
|
|
754
|
+
return undefined;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function textIncludes(haystack: string, needle: string): boolean {
|
|
759
|
+
return haystack.toLowerCase().includes(needle.toLowerCase());
|
|
760
|
+
}
|