thincoder 0.12.59 → 0.12.61
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/CHANGELOG.md +62 -4
- package/README.md +10 -8
- package/bin/thincoder.mjs +99 -133
- package/package.json +6 -4
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +45 -21
- package/src/acp.mjs +6 -1
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +59 -137
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +156 -359
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +27 -7
- package/src/agent/completion.mjs +17 -11
- package/src/agent/dispatch.mjs +145 -27
- package/src/agent/helpers.mjs +107 -13
- package/src/agent/record-results.mjs +55 -11
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +242 -0
- package/src/agent/setup-reminders.mjs +69 -0
- package/src/agent/setup.mjs +107 -127
- package/src/agent/spawn-child.mjs +55 -13
- package/src/agent-tools/advisor-async.mjs +346 -0
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +167 -116
- package/src/agent-tools/async-settle.mjs +191 -0
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +139 -107
- package/src/agent-tools/design-token.mjs +117 -0
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +24 -29
- package/src/agent-tools/escalate-async.mjs +289 -0
- package/src/agent-tools/read-history.mjs +166 -32
- package/src/agent-tools/recent-changes.mjs +2 -1
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +137 -34
- package/src/agent-tools/subagent-actions.mjs +180 -133
- package/src/agent-tools/subagent-async.mjs +184 -177
- package/src/agent-tools/subagent-panel.mjs +160 -0
- package/src/agent-tools/subagent-run.mjs +205 -0
- package/src/agent-tools/subagent-scheduler.mjs +100 -27
- package/src/agent-tools/subagent-spawn.mjs +453 -0
- package/src/agent-tools/subagent.mjs +256 -578
- package/src/agent-tools/verify.mjs +119 -292
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +89 -205
- package/src/cli/distill-command.mjs +12 -6
- package/src/cli/make-agent.mjs +26 -8
- package/src/cli/memory-command.mjs +4 -3
- package/src/cli/permission.mjs +2 -2
- package/src/cli/setup-wizard.mjs +42 -17
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +180 -63
- package/src/context.mjs +5 -147
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +128 -0
- package/src/distill.mjs +11 -11
- package/src/expand-home.mjs +16 -0
- package/src/explore-distill.mjs +155 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +72 -32
- package/src/memory/core.mjs +6 -193
- package/src/memory/delete.mjs +236 -0
- package/src/memory/docs.mjs +68 -54
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/schema.mjs +15 -3
- package/src/memory.mjs +3 -1
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/peer-domains.mjs +265 -0
- package/src/peer-instances.mjs +231 -0
- package/src/prompt-overlays.mjs +82 -0
- package/src/prompts/advisor-design.md +18 -75
- package/src/prompts/advisor-round1.md +14 -67
- package/src/prompts/advisor-round2.md +15 -51
- package/src/prompts/advisor-round3.md +15 -51
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +5 -23
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +55 -0
- package/src/prompts/persona-engineering.md +54 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +18 -98
- package/src/provider/errors.mjs +101 -0
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/retry.mjs +8 -45
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- package/src/session-gc.mjs +214 -0
- package/src/session-guard.mjs +47 -0
- package/src/session-rename.mjs +38 -0
- package/src/session-slots.mjs +188 -60
- package/src/session.mjs +104 -124
- package/src/token-ttl.mjs +274 -0
- package/src/tools/{system.mjs → bash.mjs} +19 -221
- package/src/tools/checklist-sync.mjs +181 -0
- package/src/tools/checklist.mjs +52 -39
- package/src/tools/edit-batch.mjs +109 -10
- package/src/tools/edit-diff.mjs +110 -27
- package/src/tools/edit.md +17 -12
- package/src/tools/execute.mjs +31 -4
- package/src/tools/file.mjs +41 -16
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +23 -34
- package/src/tools/glob-dialect.mjs +130 -0
- package/src/tools/glob.md +3 -3
- package/src/tools/grep.md +1 -1
- package/src/tools/index.mjs +9 -8
- package/src/tools/ops.mjs +188 -3
- package/src/tools/patch.mjs +3 -3
- package/src/tools/question.md +4 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -2
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/tools/shared.mjs +14 -13
- package/src/tools/wait_for.md +22 -0
- package/src/tui/agent-turn.mjs +36 -228
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-config.mjs +142 -30
- package/src/tui/cmd-eng.mjs +28 -40
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-mcp.mjs +8 -2
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-new.mjs +3 -2
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-session.mjs +19 -4
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-think.mjs +10 -10
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/cmd-upgrade.mjs +19 -4
- package/src/tui/config-helpers.mjs +28 -16
- package/src/tui/distill-cmd.mjs +1 -1
- package/src/tui/index.mjs +40 -38
- package/src/tui/interaction.mjs +3 -3
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +52 -9
- package/src/tui/pickers.mjs +28 -392
- package/src/tui/render-frame.mjs +32 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +12 -9
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/startup.mjs +4 -0
- package/src/tui/subagent-blocks.mjs +106 -295
- package/src/tui/subagent-children.mjs +162 -0
- package/src/tui/subagent-freeze.mjs +169 -0
- package/src/tui/subagent-panel.mjs +24 -31
- package/src/tui/suspension-drive.mjs +297 -0
- package/src/tui/tool-args.mjs +7 -5
- package/src/tui/tool-display.mjs +143 -0
- package/src/tui/tool-events.mjs +56 -185
- package/src/tui/tui-lifecycle.mjs +46 -4
- package/src/tui/update-notice.mjs +4 -0
- package/src/tui/wizard.mjs +61 -21
- package/src/tui/wrapped-spawn.mjs +38 -0
- package/src/prompts/coder.md +0 -56
- package/src/prompts/discipline.md +0 -102
- package/src/prompts/eng-coder.md +0 -44
- package/src/prompts/engineering-sub.md +0 -34
- package/src/prompts/engineering.md +0 -340
- package/src/prompts/explore.md +0 -21
- package/src/prompts/main.md +0 -56
- package/src/prompts/methodology-template.md +0 -58
- package/src/prompts/plan.md +0 -16
- package/src/prompts/system.md +0 -63
- package/src/tools/pdf-parse-text.mjs +0 -497
- package/src/tools/pdf-parse-xref.mjs +0 -499
- package/src/tools/pdf.mjs +0 -155
- package/src/tools/read_pdf.md +0 -21
|
@@ -1,64 +1,26 @@
|
|
|
1
|
-
import { isDocFile } from "../advisor/repos.mjs"
|
|
2
|
-
import { execSync, spawn, spawnSync } from "node:child_process"
|
|
3
|
-
import { readFileSync, existsSync } from "node:fs"
|
|
4
|
-
import { dirname, join, resolve } from "node:path"
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* verify.mjs — verify tool: generic pre-completion verification gate (VERIFY-REDESIGN.md).
|
|
3
|
+
* Verify is language/framework/project agnostic: it does NOT hardcode any project's
|
|
4
|
+
* module→test mapping, does NOT auto-run any test/verification command, and does NOT
|
|
5
|
+
* require a test for every change. The model DECLARES its verification status via the
|
|
6
|
+
* `verification` argument (whether it already ran the project's verification and the
|
|
7
|
+
* result); verify makes a mechanical gate decision on that declaration. Which
|
|
8
|
+
* verification to run is described in natural language in each project's AGENTS.md —
|
|
9
|
+
* verify never parses or executes it. Retained mechanical checks: doc-only fast path,
|
|
10
|
+
* an advisory (non-gating) node --check hint, git diff report, and — on the code
|
|
11
|
+
* path (doc-only changes return early) — the task list and self-review checklist.
|
|
10
12
|
*/
|
|
11
|
-
const MODULE_TO_TEST = {
|
|
12
|
-
tools: [
|
|
13
|
-
"test/file-tools.test.mjs", "test/edit-tools.test.mjs", "test/git.test.mjs",
|
|
14
|
-
"test/checkpoint.test.mjs", "test/bash.test.mjs", "test/ops-scope.test.mjs",
|
|
15
|
-
"test/execute.test.mjs", "test/checklist.test.mjs", "test/skills-distill.test.mjs",
|
|
16
|
-
"test/verify-domain.test.mjs", "test/pdf-parse.test.mjs",
|
|
17
|
-
],
|
|
18
|
-
"agent-tools": [
|
|
19
|
-
"test/subagent-core.test.mjs", "test/subagent-async.test.mjs", "test/subagent-tool.test.mjs",
|
|
20
|
-
"test/subagent-panel.test.mjs", "test/subagent-scheduler.test.mjs", "test/verify-domain.test.mjs",
|
|
21
|
-
],
|
|
22
|
-
agent: [
|
|
23
|
-
"test/agent-core.test.mjs", "test/agent-context.test.mjs", "test/guards.test.mjs",
|
|
24
|
-
"test/eng-delivery.test.mjs", "test/eng-reminders.test.mjs", "test/provider-stream.test.mjs",
|
|
25
|
-
"test/stream-rules.test.mjs", "test/session-compaction.test.mjs",
|
|
26
|
-
"test/skills-distill.test.mjs", "test/verify-domain.test.mjs", "test/dispatch.test.mjs",
|
|
27
|
-
],
|
|
28
|
-
memory: ["test/memory.test.mjs"],
|
|
29
|
-
tui: ["test/tui-render.test.mjs", "test/tui-input.test.mjs", "test/tui-panel.test.mjs", "test/tui-stream.test.mjs", "test/tui-picker.test.mjs", "test/subagent-blocks.test.mjs"],
|
|
30
|
-
provider: ["test/integration-provider.mjs"],
|
|
31
|
-
config: ["test/integration-provider.mjs"],
|
|
32
|
-
skills: ["test/skills-distill.test.mjs"],
|
|
33
|
-
distill: ["test/skills-distill.test.mjs"],
|
|
34
|
-
markdown: ["test/memory.test.mjs"],
|
|
35
|
-
advisor: ["test/advisor-message.test.mjs", "test/advisor-review.test.mjs", "test/advisor-eng.test.mjs", "test/advisor-parallel.test.mjs"],
|
|
36
|
-
prompts: ["test/prompts.test.mjs"],
|
|
37
|
-
context: ["test/agent-context.test.mjs", "test/session-compaction.test.mjs"],
|
|
38
|
-
session: ["test/session.test.mjs", "test/session-compaction.test.mjs", "test/session-eng-advisor.test.mjs", "test/session-migration.test.mjs", "test/session-safety.test.mjs", "test/acp.test.mjs"],
|
|
39
|
-
mcp: null,
|
|
40
|
-
}
|
|
41
13
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* "D:/proj/src/agent/helpers.mjs" → "agent"
|
|
47
|
-
*/
|
|
48
|
-
function moduleName(srcPath) {
|
|
49
|
-
const norm = srcPath.replace(/\\/g, "/")
|
|
50
|
-
const srcIdx = norm.lastIndexOf("/src/")
|
|
51
|
-
const rel = srcIdx === -1 ? norm.replace(/^src\//, "") : norm.slice(srcIdx + 5)
|
|
52
|
-
const firstSlash = rel.search(/[/\\]/)
|
|
53
|
-
if (firstSlash === -1) return rel.replace(/\.mjs$/, "")
|
|
54
|
-
return rel.slice(0, firstSlash)
|
|
55
|
-
}
|
|
14
|
+
import { isCodePath, isDocPath, loadConventions } from "../conventions.mjs"
|
|
15
|
+
import { execSync, spawnSync } from "node:child_process"
|
|
16
|
+
import { existsSync } from "node:fs"
|
|
17
|
+
import { resolve } from "node:path"
|
|
56
18
|
|
|
57
19
|
/**
|
|
58
20
|
* §18.12 D-VR1 path normalization — mirrors the §20.5 file-domain handling:
|
|
59
21
|
* resolved to an absolute path with forward slashes; dedup uses the win32
|
|
60
22
|
* lowercase comparison key (changedFileKey) so "D:/x/src/a.mjs" and
|
|
61
|
-
* "d
|
|
23
|
+
* "d:\\x\SRC\a.mjs" count as the same file.
|
|
62
24
|
*/
|
|
63
25
|
function normalizeChangedPath(p, base) {
|
|
64
26
|
return resolve(base, p).replace(/\\/g, "/")
|
|
@@ -70,64 +32,51 @@ function changedFileKey(p) {
|
|
|
70
32
|
}
|
|
71
33
|
|
|
72
34
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* Walk stops at the filesystem root; returns null when no anchor exists.
|
|
77
|
-
*/
|
|
78
|
-
function findProjectRoot(absPath) {
|
|
79
|
-
let dir = dirname(resolve(absPath))
|
|
80
|
-
for (;;) {
|
|
81
|
-
if (existsSync(join(dir, "package.json")) || existsSync(join(dir, ".git"))) return dir
|
|
82
|
-
const parent = dirname(dir)
|
|
83
|
-
if (parent === dir) return null
|
|
84
|
-
dir = parent
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* True when the path is under <projectRoot>/src/ (anchored via findProjectRoot —
|
|
90
|
-
* a parent-path /src/ segment outside the project must not classify a file as
|
|
91
|
-
* product code). Falls back to the loose /src/ pattern when no anchor exists.
|
|
35
|
+
* D-V3 block guidance — a block message must not just say "not verified": it lists
|
|
36
|
+
* the changed code files and points to the project's AGENTS.md natural-language
|
|
37
|
+
* description of how to verify, so the model knows what to run/add.
|
|
92
38
|
*/
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
39
|
+
function appendBlockGuidance(lines, codeFiles) {
|
|
40
|
+
lines.push("")
|
|
41
|
+
lines.push("Changed code file(s):")
|
|
42
|
+
for (const f of codeFiles) lines.push(` ${f}`)
|
|
43
|
+
lines.push("")
|
|
44
|
+
lines.push("verify does not run commands for you — decide and run the project's verification yourself.")
|
|
45
|
+
lines.push("Reference the verification approach declared in the project's AGENTS.md (natural language, e.g. \"tests run with npm test\" / \"no automated tests — rely on manual review\") to decide what to run, then call verify again declaring the outcome via verification.status.")
|
|
98
46
|
}
|
|
99
47
|
|
|
100
48
|
/**
|
|
101
|
-
* verify tool: pre-completion
|
|
102
|
-
* 0. Doc-only fast path — all changed files are docs
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* 4.
|
|
108
|
-
*
|
|
109
|
-
* Default runs syntax checks + related tests; full=true runs the entire test suite.
|
|
110
|
-
* Agent must not say "done" before verify passes. Fix-verify loop at most MAX_VERIFY_RETRIES rounds.
|
|
49
|
+
* verify tool: generic pre-completion verification gate. Flow:
|
|
50
|
+
* 0. Doc-only fast path — all changed files are docs: short report, no verification needed.
|
|
51
|
+
* 1. git diff --stat — changed file list (_touchedFiles ∪ git diff)
|
|
52
|
+
* 2. Advisory syntax hint (D-V5) — node --check changed .js/.mjs when node exists; SOFT, not gating.
|
|
53
|
+
* 3. Verification gate (D-V1/D-V2) — requires the model to DECLARE verification.status:
|
|
54
|
+
* passed → allowed; failed → blocked; skipped → allowed but needs a summary reason.
|
|
55
|
+
* 4. Task list + self-review checklist.
|
|
56
|
+
* Agent must not say "done" before verify passes.
|
|
111
57
|
*/
|
|
112
58
|
export const verifyTool = {
|
|
113
59
|
name: "verify",
|
|
114
60
|
description:
|
|
115
|
-
"
|
|
116
|
-
"Returns the check report — syntax results, related-test outcomes, task list; verify failure blocks the completion claim.",
|
|
61
|
+
"Generic pre-completion verification gate — language/framework/project agnostic. verify does NOT run any test/verification command for you and does NOT require a test per change. You DECLARE your verification status via the verification argument: { status: 'passed' | 'failed' | 'skipped', command?, summary? }. 'passed' → allowed; 'failed' → blocked (cannot claim done); 'skipped' → allowed but requires a summary reason. Whether/how to verify is described in natural language in the project's AGENTS.md and is decided + executed by you — verify only mechanically gates on your declaration. Also reports changed files (git diff), an advisory node --check hint on changed .js/.mjs (not gating), the task list, and a self-review checklist. Call this BEFORE declaring a coding task complete — do not say 'done' until verify passes.",
|
|
117
62
|
parameters: {
|
|
118
63
|
type: "object",
|
|
119
64
|
properties: {
|
|
120
|
-
|
|
65
|
+
verification: {
|
|
66
|
+
type: "object",
|
|
67
|
+
description: "The model's declaration of whether it already ran the project's verification and its outcome. verify does not execute the command — it is self-reported evidence.",
|
|
68
|
+
properties: {
|
|
69
|
+
status: { type: "string", enum: ["passed", "failed", "skipped"], description: "The verification outcome you declare: 'passed' → allowed; 'failed' → blocked; 'skipped' → allowed but requires a summary reason." },
|
|
70
|
+
command: { type: "string", description: "Optional: the verification command you ran (self-reported — verify does not execute it)." },
|
|
71
|
+
summary: { type: "string", description: "Optional: summary/result of the verification. Required when status='skipped' — a concrete reason (e.g. \"project has no automated tests — verified by manual review\")." },
|
|
72
|
+
},
|
|
73
|
+
required: ["status"],
|
|
74
|
+
},
|
|
121
75
|
workdir: { type: "string", description: "Optional: run verify in this subdirectory (relative to cwd or absolute) — for monorepos" },
|
|
122
|
-
testNamePattern: { type: "string", description: "Optional: limit the test run to matching test names (node --test-name-pattern / npm test -- --test-name-pattern). Renamed from filter — the old name is rejected with an error." },
|
|
123
76
|
},
|
|
124
77
|
},
|
|
125
78
|
readonly: true,
|
|
126
|
-
outputPanel: true, // stream test output to a panel instead of inline
|
|
127
79
|
async execute(args, ctx) {
|
|
128
|
-
if ("filter" in args) {
|
|
129
|
-
throw new Error("filter was renamed to testNamePattern — use testNamePattern; the old name is rejected")
|
|
130
|
-
}
|
|
131
80
|
const cwd = ctx.agent.cwd
|
|
132
81
|
// Changed-file resolution (§18.12 D-VR3): _touchedFiles (per-run bookkeeping,
|
|
133
82
|
// absolute paths) ∪ git diff fallback — git is tried at testCwd
|
|
@@ -183,150 +132,111 @@ export const verifyTool = {
|
|
|
183
132
|
return true
|
|
184
133
|
})
|
|
185
134
|
|
|
135
|
+
// D-V11 G10 (doc-only/empty change set must not swallow an explicit failed):
|
|
136
|
+
// an explicit verification.status='failed' is ALWAYS respected — never
|
|
137
|
+
// overridden to pass by the doc-only fast path or the no-code-changed path
|
|
138
|
+
// below (D-V5 exception, review #6). Aligns CLI with VS Code (T-V8). Lists
|
|
139
|
+
// the changed files + points at AGENTS.md (D-V3 guidance).
|
|
140
|
+
if (args.verification?.status === "failed") {
|
|
141
|
+
lines.push("")
|
|
142
|
+
lines.push("VERIFY BLOCKED: you declared verification failed (verification.status = 'failed').")
|
|
143
|
+
lines.push("Do not say 'done' while verification fails — fix the issue, re-run the project's verification, then call verify again declaring the outcome.")
|
|
144
|
+
lines.push("")
|
|
145
|
+
lines.push("Changed file(s):")
|
|
146
|
+
for (const f of changedFiles) lines.push(` ${f}`)
|
|
147
|
+
lines.push("")
|
|
148
|
+
lines.push("verify does not run commands for you — decide and run the project's verification yourself.")
|
|
149
|
+
lines.push("Reference the verification approach declared in the project's AGENTS.md (natural language, e.g. \"tests run with npm test\" / \"no automated tests — rely on manual review\") to decide what to run, then call verify again declaring the outcome via verification.status.")
|
|
150
|
+
ctx.agent._verifyPassed = false
|
|
151
|
+
return lines.join("\n")
|
|
152
|
+
}
|
|
153
|
+
|
|
186
154
|
// 1b. Doc-only fast path: every changed file is documentation (docs/, *.md,
|
|
187
|
-
// LICENSE…) — syntax checks and
|
|
188
|
-
// the task list/self-review checklist add nothing either.
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// Empty list (no changes / git unavailable)
|
|
193
|
-
// to the normal path below.
|
|
194
|
-
|
|
155
|
+
// LICENSE…) — syntax checks and a verification declaration are meaningless
|
|
156
|
+
// for doc changes, and the task list/self-review checklist add nothing either.
|
|
157
|
+
// Paths inside a declared code segment (default: src — incl. prompts/*.md) are
|
|
158
|
+
// product code — excluded from the fast path, consistent with the design gate.
|
|
159
|
+
// The project's own layout is declarable (.thincoder/conventions.json).
|
|
160
|
+
// Empty list (no changes / git unavailable)
|
|
161
|
+
// intentionally falls through to the normal path below.
|
|
162
|
+
const conv = loadConventions(cwd)
|
|
163
|
+
if (changedFiles.length > 0 && changedFiles.every((f) => isDocPath(f, conv))) {
|
|
195
164
|
lines.push("")
|
|
196
165
|
lines.push("Documentation-only changes — skipping syntax checks and tests.")
|
|
197
166
|
ctx.agent._verifyPassed = true
|
|
198
167
|
return lines.join("\n")
|
|
199
168
|
}
|
|
200
169
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
170
|
+
// Code files needing verification: anything the shared classifier calls product
|
|
171
|
+
// code (D-V1 — these require a verification declaration). Temp scratch files are
|
|
172
|
+
// neither code nor docs and fall out of this list (a throwaway diagnostic script
|
|
173
|
+
// is not a code change — no declaration is demanded for it).
|
|
174
|
+
const codeFiles = changedFiles.filter((f) => isCodePath(f, conv))
|
|
175
|
+
|
|
176
|
+
// 2. Advisory syntax hint (D-V5) — node --check on changed .js/.mjs when node
|
|
177
|
+
// exists. SOFT hint only — it does NOT gate done (no language-specific
|
|
178
|
+
// enforcement; a non-JS project or a missing node simply skips silently).
|
|
179
|
+
const jsFiles = codeFiles.filter((f) => /\.(m?js)$/i.test(f) && existsSync(f))
|
|
204
180
|
if (jsFiles.length > 0) {
|
|
205
181
|
lines.push("")
|
|
206
|
-
lines.push("
|
|
182
|
+
lines.push("Advisory syntax hint (node --check — does not gate):")
|
|
207
183
|
for (const f of jsFiles) {
|
|
208
|
-
const abs = resolve(cwd, f)
|
|
209
|
-
if (!existsSync(abs)) continue // skip deleted files
|
|
210
184
|
try {
|
|
211
|
-
const result = spawnSync("node", ["--check",
|
|
185
|
+
const result = spawnSync("node", ["--check", f], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 10000 })
|
|
212
186
|
if (result.status !== 0) throw result
|
|
213
187
|
lines.push(` ✓ ${f}`)
|
|
214
188
|
} catch (e) {
|
|
215
|
-
syntaxFailed = true
|
|
216
189
|
const errOutput = (e.stderr || e.stdout || e.message || "").toString()
|
|
217
190
|
const errMsg = errOutput.split("\n").slice(0, 3).join("\n")
|
|
218
|
-
lines.push(`
|
|
191
|
+
lines.push(` ⚠ ${f} — node --check flagged it`)
|
|
219
192
|
lines.push(` ${errMsg.replace(/\n/g, "\n ")}`)
|
|
220
193
|
}
|
|
221
194
|
}
|
|
222
|
-
if (!syntaxFailed) lines.push(" All syntax checks passed.")
|
|
223
195
|
}
|
|
224
196
|
|
|
225
|
-
// 3.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
197
|
+
// 3. Verification gate (D-V1/D-V2): the model declares its verification status.
|
|
198
|
+
if (codeFiles.length === 0) {
|
|
199
|
+
// No code changed (empty change set / only temp/non-code files) — nothing to verify.
|
|
200
|
+
lines.push("")
|
|
201
|
+
lines.push("No code files changed — nothing to verify.")
|
|
202
|
+
ctx.agent._verifyPassed = true
|
|
203
|
+
} else {
|
|
204
|
+
const v = args.verification ?? {}
|
|
205
|
+
const status = v?.status
|
|
206
|
+
if (!status) {
|
|
207
|
+
lines.push("")
|
|
208
|
+
lines.push("VERIFY BLOCKED: no verification status was declared.")
|
|
209
|
+
lines.push("These code changes must be verified before you say 'done' — declare whether you ran the project's verification and its result.")
|
|
210
|
+
appendBlockGuidance(lines, codeFiles)
|
|
211
|
+
ctx.agent._verifyPassed = false
|
|
212
|
+
} else if (status === "passed") {
|
|
237
213
|
lines.push("")
|
|
238
|
-
lines.push("
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
214
|
+
lines.push("Verification declared passed.")
|
|
215
|
+
if (v.command) lines.push(` command: ${v.command}`)
|
|
216
|
+
if (v.summary) lines.push(` summary: ${v.summary}`)
|
|
217
|
+
ctx.agent._verifyPassed = true
|
|
218
|
+
} else if (status === "skipped") {
|
|
219
|
+
const reason = (v.summary ?? "").trim()
|
|
220
|
+
if (reason) {
|
|
221
|
+
lines.push("")
|
|
222
|
+
lines.push(`Verification skipped with reason: ${reason}`)
|
|
223
|
+
ctx.agent._verifyPassed = true
|
|
243
224
|
} else {
|
|
244
|
-
lines.push("
|
|
225
|
+
lines.push("")
|
|
226
|
+
lines.push("VERIFY BLOCKED: verification skipped with no summary reason — an empty skip is not allowed.")
|
|
227
|
+
lines.push("Give a concrete reason in verification.summary (e.g. \"project has no automated tests — verified by manual review\"), or run the project's verification and declare its result.")
|
|
228
|
+
appendBlockGuidance(lines, codeFiles)
|
|
245
229
|
ctx.agent._verifyPassed = false
|
|
246
230
|
}
|
|
247
231
|
} else {
|
|
232
|
+
// Unknown status value — defensive (schema enum normally rejects it first).
|
|
248
233
|
lines.push("")
|
|
249
|
-
lines.push(
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
} else if (relatedTests.length > 0) {
|
|
253
|
-
// Default mode: run only related test files
|
|
254
|
-
lines.push("")
|
|
255
|
-
lines.push(`Related tests (${relatedTests.length} file(s) for modules: ${modules.join(", ")}):`)
|
|
256
|
-
let anyTestFailed = false
|
|
257
|
-
let anyTestMissing = false
|
|
258
|
-
for (const testFile of relatedTests) {
|
|
259
|
-
// Lookup: cwd first (existing behavior), then the changed files' project
|
|
260
|
-
// root (§18.12 F-VR1 — subagent cwd may be a workspace root, not the repo
|
|
261
|
-
// root; the mapped test file lives at <projectRoot>/test/...).
|
|
262
|
-
let testAbs = join(cwd, testFile)
|
|
263
|
-
let testRunCwd = cwd
|
|
264
|
-
if (!existsSync(testAbs)) {
|
|
265
|
-
const roots = [...new Set(srcFiles.map(findProjectRoot).filter(Boolean))]
|
|
266
|
-
const inRoot = roots.map((r) => ({ root: r, p: join(r, testFile) })).find((x) => existsSync(x.p))
|
|
267
|
-
if (inRoot) {
|
|
268
|
-
testAbs = inRoot.p
|
|
269
|
-
testRunCwd = inRoot.root
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
if (!existsSync(testAbs)) {
|
|
273
|
-
anyTestMissing = true
|
|
274
|
-
lines.push(` ? ${testFile} — file not found (cwd + changed files' project root), verify did not run it`)
|
|
275
|
-
continue
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
const result = await runTestFile(testRunCwd, testAbs, ctx, args.testNamePattern)
|
|
279
|
-
if (result.passed) {
|
|
280
|
-
lines.push(` ✓ ${testFile}`)
|
|
281
|
-
} else {
|
|
282
|
-
anyTestFailed = true
|
|
283
|
-
lines.push(` ✗ ${testFile} — FAILED`)
|
|
284
|
-
lines.push(` ${result.tail.replace(/\n/g, "\n ")}`)
|
|
285
|
-
}
|
|
286
|
-
} catch (e) {
|
|
287
|
-
anyTestFailed = true
|
|
288
|
-
lines.push(` ✗ ${testFile} — error: ${e.message}`)
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (anyTestFailed) {
|
|
292
|
-
lines.push("")
|
|
293
|
-
lines.push("✗ Related tests FAILED. Review the output above, fix the issues, then run verify again.")
|
|
294
|
-
ctx.agent._verifyPassed = false
|
|
295
|
-
} else if (anyTestMissing) {
|
|
296
|
-
lines.push("")
|
|
297
|
-
lines.push("✗ Related test file(s) MISSING — verify did NOT certify this change. Locate the test file or fix MODULE_TO_TEST.")
|
|
234
|
+
lines.push(`VERIFY BLOCKED: unknown verification.status ${JSON.stringify(status)} — use 'passed' | 'failed' | 'skipped'.`)
|
|
235
|
+
appendBlockGuidance(lines, codeFiles)
|
|
298
236
|
ctx.agent._verifyPassed = false
|
|
299
|
-
} else {
|
|
300
|
-
lines.push(" All related tests passed.")
|
|
301
|
-
ctx.agent._verifyPassed = !syntaxFailed
|
|
302
|
-
}
|
|
303
|
-
} else {
|
|
304
|
-
// No related tests found for the changed modules
|
|
305
|
-
const uncovered = modules.filter((m) => !MODULE_TO_TEST[m] || (Array.isArray(MODULE_TO_TEST[m]) && MODULE_TO_TEST[m].length === 0))
|
|
306
|
-
const untested = modules.filter((m) => !(m in MODULE_TO_TEST))
|
|
307
|
-
lines.push("")
|
|
308
|
-
if (uncovered.length > 0) {
|
|
309
|
-
lines.push(`Related tests: NONE for module(s) ${uncovered.join(", ")} — these modules have no dedicated test file.`)
|
|
310
|
-
lines.push("ACTION REQUIRED: write a test that covers the change you just made.")
|
|
311
|
-
lines.push("Do NOT proceed to 'done' — a test file is required before this change is complete.")
|
|
312
|
-
} else if (untested.length > 0) {
|
|
313
|
-
lines.push(`Related tests: NONE for module(s) ${untested.join(", ")} — unknown module, no test mapping exists.`)
|
|
314
|
-
lines.push("ACTION REQUIRED: determine which test file covers this code and add it to MODULE_TO_TEST, or write a new test.")
|
|
315
|
-
} else if (srcFiles.length === 0) {
|
|
316
|
-
lines.push("Related tests: no source .mjs files changed — nothing to test.")
|
|
317
|
-
ctx.agent._verifyPassed = !syntaxFailed
|
|
318
|
-
} else {
|
|
319
|
-
lines.push("Related tests: none matched. Run verify with full=true to run the full suite.")
|
|
320
|
-
ctx.agent._verifyPassed = !syntaxFailed
|
|
321
237
|
}
|
|
322
238
|
}
|
|
323
239
|
|
|
324
|
-
// Show full-suite hint when not run
|
|
325
|
-
if (!args.full && hasTestScript) {
|
|
326
|
-
lines.push("")
|
|
327
|
-
lines.push("Note: full test suite not run. Use verify full=true to run ALL tests before committing.")
|
|
328
|
-
}
|
|
329
|
-
|
|
330
240
|
// 4. Task list
|
|
331
241
|
lines.push("")
|
|
332
242
|
if (ctx.agent.tasks.length === 0) {
|
|
@@ -349,7 +259,7 @@ export const verifyTool = {
|
|
|
349
259
|
// 5. Checklist
|
|
350
260
|
lines.push("")
|
|
351
261
|
lines.push("Self-review checklist:")
|
|
352
|
-
lines.push("- [ ] Did I run the project's
|
|
262
|
+
lines.push("- [ ] Did I run the project's verification (per its AGENTS.md) and does it pass, or did I declare a concrete skip reason?")
|
|
353
263
|
lines.push("- [ ] Did I read every file I changed to catch leftover debug code or stale comments?")
|
|
354
264
|
lines.push("- [ ] Do comments and docstrings match what the code actually does?")
|
|
355
265
|
lines.push("- [ ] Did I remove placeholder code, TODO stubs, or commented-out experiment blocks?")
|
|
@@ -359,86 +269,3 @@ export const verifyTool = {
|
|
|
359
269
|
return lines.join("\n")
|
|
360
270
|
},
|
|
361
271
|
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Run a single test file with node --test, no maxBuffer limit.
|
|
365
|
-
* Returns { passed: boolean, tail: string } — the last few lines of output.
|
|
366
|
-
*/
|
|
367
|
-
function runTestFile(cwd, testPath, ctx, filter) {
|
|
368
|
-
return new Promise((resolve, reject) => {
|
|
369
|
-
const child = spawn("node", filter ? ["--test", "--test-name-pattern", filter, testPath] : ["--test", testPath], {
|
|
370
|
-
cwd, stdio: ["ignore", "pipe", "pipe"],
|
|
371
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
372
|
-
})
|
|
373
|
-
let stdout = ""
|
|
374
|
-
let stderr = ""
|
|
375
|
-
child.stdout.on("data", (d) => {
|
|
376
|
-
const s = d.toString()
|
|
377
|
-
stdout += s
|
|
378
|
-
ctx.callbacks?.onToolOutput?.("verify", s)
|
|
379
|
-
})
|
|
380
|
-
child.stderr.on("data", (d) => {
|
|
381
|
-
const s = d.toString()
|
|
382
|
-
stderr += s
|
|
383
|
-
ctx.callbacks?.onToolOutput?.("verify", s)
|
|
384
|
-
})
|
|
385
|
-
const timer = setTimeout(() => {
|
|
386
|
-
child.kill("SIGKILL")
|
|
387
|
-
reject(new Error(`Test ${testPath} timed out after 120s`))
|
|
388
|
-
}, 120000)
|
|
389
|
-
child.on("error", (e) => {
|
|
390
|
-
clearTimeout(timer)
|
|
391
|
-
reject(e)
|
|
392
|
-
})
|
|
393
|
-
child.on("close", (code) => {
|
|
394
|
-
clearTimeout(timer)
|
|
395
|
-
const output = (stdout + stderr).trim()
|
|
396
|
-
const tail = output.split("\n").slice(-8).join("\n")
|
|
397
|
-
resolve({ passed: code === 0, tail })
|
|
398
|
-
})
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Run npm test via spawn, no maxBuffer limit.
|
|
404
|
-
* Test output is streamed through ctx.callbacks.onToolOutput (TUI can display progress in real time).
|
|
405
|
-
* Returns { passed: boolean, tail: string }.
|
|
406
|
-
*/
|
|
407
|
-
function runTestSuite(cwd, ctx, filter) {
|
|
408
|
-
return new Promise((resolve, reject) => {
|
|
409
|
-
const child = spawn("npm", filter ? ["test", "--", `--test-name-pattern=${filter}`] : ["test"], {
|
|
410
|
-
cwd, shell: true, stdio: ["ignore", "pipe", "pipe"],
|
|
411
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
412
|
-
})
|
|
413
|
-
let stdout = ""
|
|
414
|
-
let stderr = ""
|
|
415
|
-
child.stdout.on("data", (d) => {
|
|
416
|
-
const s = d.toString()
|
|
417
|
-
stdout += s
|
|
418
|
-
ctx.callbacks?.onToolOutput?.("verify", s)
|
|
419
|
-
})
|
|
420
|
-
child.stderr.on("data", (d) => {
|
|
421
|
-
const s = d.toString()
|
|
422
|
-
stderr += s
|
|
423
|
-
ctx.callbacks?.onToolOutput?.("verify", s)
|
|
424
|
-
})
|
|
425
|
-
const timer = setTimeout(() => {
|
|
426
|
-
child.kill("SIGKILL")
|
|
427
|
-
const err = new Error("Tests timed out after 120s")
|
|
428
|
-
err.stdout = stdout
|
|
429
|
-
err.stderr = stderr
|
|
430
|
-
reject(err)
|
|
431
|
-
}, 120000)
|
|
432
|
-
child.on("error", (e) => {
|
|
433
|
-
clearTimeout(timer)
|
|
434
|
-
reject(e)
|
|
435
|
-
})
|
|
436
|
-
child.on("close", (code) => {
|
|
437
|
-
clearTimeout(timer)
|
|
438
|
-
const output = (stdout + stderr).trim()
|
|
439
|
-
const tail = output.split("\n").slice(-8).join("\n")
|
|
440
|
-
resolve({ passed: code === 0, tail })
|
|
441
|
-
})
|
|
442
|
-
})
|
|
443
|
-
}
|
|
444
|
-
|
package/src/agent-tools.mjs
CHANGED
|
@@ -14,3 +14,4 @@ export { timerTool } from "./agent-tools/timer.mjs"
|
|
|
14
14
|
export { advisorTool } from "./agent-tools/advisor.mjs"
|
|
15
15
|
export { engTool } from "./agent-tools/eng.mjs"
|
|
16
16
|
export { readHistoryTool } from "./agent-tools/read-history.mjs"
|
|
17
|
+
export { batchSegmentTool } from "./agent-tools/batch-segment.mjs"
|