simt-dev 0.7.1 → 0.8.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/CHANGELOG.md +36 -1
- package/README.md +169 -80
- package/docs/decisions/ADR-014-workspace-is-configuration.md +1 -1
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
- package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +3 -1
- package/rules/mdc/intake-questions.mdc +8 -0
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +7 -5
- package/src/argv.mjs +19 -0
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +155 -113
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +12 -26
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +17 -535
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +15 -1037
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +116 -971
- package/src/bench/run.mjs +164 -881
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +11 -13
- package/src/ci/dependabot.mjs +25 -55
- package/src/commands/agent.mjs +1 -9
- package/src/commands/agenthook.mjs +66 -35
- package/src/commands/bench.mjs +2 -2
- package/src/commands/ci.mjs +11 -13
- package/src/commands/docs.mjs +88 -0
- package/src/commands/gate.mjs +10 -13
- package/src/commands/harness.mjs +3 -3
- package/src/commands/help.mjs +44 -3
- package/src/commands/hooks.mjs +20 -20
- package/src/commands/ignore.mjs +11 -7
- package/src/commands/impact.mjs +4 -8
- package/src/commands/index.mjs +49 -9
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +122 -104
- package/src/commands/lease.mjs +67 -18
- package/src/commands/map.mjs +10 -5
- package/src/commands/mcp.mjs +2 -17
- package/src/commands/repoindex.mjs +8 -8
- package/src/commands/rules.mjs +8 -4
- package/src/commands/shared.mjs +12 -16
- package/src/commands/skills.mjs +5 -6
- package/src/commands/skillscompile.mjs +3 -3
- package/src/commands/skillsinstall.mjs +5 -4
- package/src/commands/slice.mjs +199 -64
- package/src/commands/spec.mjs +78 -62
- package/src/commands/status.mjs +24 -12
- package/src/commands/sync.mjs +14 -14
- package/src/commands/tests.mjs +12 -12
- package/src/commands/tokens.mjs +19 -24
- package/src/commands/upgrade.mjs +8 -4
- package/src/commands/why.mjs +6 -13
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/decisions/index.mjs +3 -2
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +2 -1
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +5 -3
- package/src/hooks/dispatcher.mjs +26 -32
- package/src/hooks/gates.mjs +12 -2
- package/src/hooks/resolve.mjs +2 -7
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +2 -11
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +16 -11
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +124 -17
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +49 -9
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -1005
- package/src/{bench → measure}/corpus.mjs +1 -1
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/output/index.mjs +122 -31
- package/src/packs/manage.mjs +15 -9
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/manifest.mjs +10 -435
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +16 -547
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +13 -1
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +0 -7
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +8 -12
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/{bench/process.mjs → sys/proc.mjs} +28 -5
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +1 -1
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +3 -1
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- package/src/bench/tasks.mjs +0 -36
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "gemini",
|
|
3
|
+
"loaderOrder": 6,
|
|
4
|
+
"loader": {
|
|
5
|
+
"id": "gemini",
|
|
6
|
+
"label": "Gemini CLI",
|
|
7
|
+
"readers": "Gemini CLI (google-gemini/gemini-cli)",
|
|
8
|
+
"surface": null,
|
|
9
|
+
"start": [
|
|
10
|
+
{
|
|
11
|
+
"scope": "project",
|
|
12
|
+
"path": "GEMINI.md",
|
|
13
|
+
"tree": "chain",
|
|
14
|
+
"atStart": true,
|
|
15
|
+
"why": "Gemini CLI context docs (docs/cli/gemini-md/): GEMINI.md is collected from the working directory upward through parent directories until the first directory holding a boundary marker (.git by default) or home"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"scope": "user",
|
|
19
|
+
"path": "~/.gemini/GEMINI.md",
|
|
20
|
+
"tree": "single",
|
|
21
|
+
"atStart": true,
|
|
22
|
+
"why": "Gemini CLI context docs: the user-global ~/.gemini/GEMINI.md opens the concatenation, before any project file"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"alsoReads": [
|
|
26
|
+
{
|
|
27
|
+
"what": "the context file under another name — settings.json context.fileName, a string or an array",
|
|
28
|
+
"atStart": true,
|
|
29
|
+
"why": "Gemini CLI configuration docs: context.fileName renames the context file (a string or an array), so an installation listing AGENTS.md there reads AGENTS.md as its GEMINI.md"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"what": "GEMINI.md (or the renamed context file) in subdirectories below the working directory",
|
|
33
|
+
"atStart": false,
|
|
34
|
+
"why": "Gemini CLI context docs: directories a tool touches are scanned for a context file up to a trusted root, bounded by context.discoveryMaxDirs (200) - not at session start"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"order": "user-global ~/.gemini/GEMINI.md first, then the project chain general to specific - project-root side first (upward traversal stops at the first directory holding a boundary marker, .git by default) down through each directory to the working directory - then just-in-time files a tool touch discovered; the whole result is one concatenation with origin separators",
|
|
38
|
+
"cap": null,
|
|
39
|
+
"capNote": "no byte cap is published for the merged Gemini CLI context (checked 2026-10-26)",
|
|
40
|
+
"imports": {
|
|
41
|
+
"syntax": "@path",
|
|
42
|
+
"depth": 5,
|
|
43
|
+
"why": "Gemini CLI context docs: @path imports resolve relative or absolute to a maximum depth of 5, with cycles prevented by skipping files already on the stack"
|
|
44
|
+
},
|
|
45
|
+
"nested": "general to specific, all loaded — unlike Codex, every context file on the walk contributes; a nearer file cannot hide a parent one, it just concatenates after it; subdirectory files wait for a tool touch",
|
|
46
|
+
"skills": [],
|
|
47
|
+
"skillsNote": "no project skill directory is documented for Gemini CLI; extensions are the distribution mechanism, and they are tooling not context",
|
|
48
|
+
"hooks": {
|
|
49
|
+
"path": ".gemini/settings.json",
|
|
50
|
+
"verified": "Gemini CLI hooks reference (geminicli.com/docs/hooks/reference/, read 2026-10-26): a project .gemini/settings.json carries a top-level hooks object whose events are arrays of {matcher, hooks:[{type:\"command\",command,timeout? (milliseconds)}]} — the same shape Claude and Codex use; BeforeTool denies with {decision:\"deny\",reason}, BeforeAgent and AfterTool inject via hookSpecificOutput.additionalContext"
|
|
51
|
+
},
|
|
52
|
+
"agents": {
|
|
53
|
+
"dir": ".gemini/agents",
|
|
54
|
+
"ext": ".md",
|
|
55
|
+
"verified": "Gemini CLI subagents docs (geminicli.com/docs/core/subagents, read 2026-10-30): project subagents are markdown files with name and description frontmatter under .gemini/agents/"
|
|
56
|
+
},
|
|
57
|
+
"mcp": {
|
|
58
|
+
"path": ".gemini/settings.json",
|
|
59
|
+
"key": "mcpServers",
|
|
60
|
+
"verified": "Gemini CLI configuration docs (docs/reference/configuration/, read 2026-10-26): a project .gemini/settings.json (a user ~/.gemini/settings.json merged behind it, objects merged key by key) carries a top-level mcpServers object whose entries name a command, a url or an httpUrl"
|
|
61
|
+
},
|
|
62
|
+
"verified": "Gemini CLI context and configuration docs (geminicli.com/docs/cli/gemini-md/ and docs/reference/configuration/, read 2026-10-26) — settings live at project .gemini/settings.json and user ~/.gemini/settings.json, merged for objects; the context is user ~/.gemini/GEMINI.md plus the context file (GEMINI.md unless context.fileName renames it, an array is allowed) from the working directory upward through parents to the first .git or home, with @path imports to depth 5; server registration is the mcpServers object in those same settings files; no byte cap is published",
|
|
63
|
+
"note": "The configuration reference this table once recorded as 404ing is live again as of 2026-10-26, which is why the row is full instead of thin. What still shapes a byte count is the pair of absences the docs confirm rather than deny: no cap, and no promise that AGENTS.md is read at all — context.fileName decides that, and it may name anything."
|
|
64
|
+
},
|
|
65
|
+
"hooks": {
|
|
66
|
+
"id": "gemini",
|
|
67
|
+
"event": "BeforeTool",
|
|
68
|
+
"matcher": "run_shell_command|write_file|replace",
|
|
69
|
+
"timeout": 10000,
|
|
70
|
+
"denyExit": 0,
|
|
71
|
+
"verified": "geminicli.com/docs/hooks/reference/, read 2026-10-26",
|
|
72
|
+
"prompt": {
|
|
73
|
+
"event": "BeforeAgent",
|
|
74
|
+
"verified": "geminicli.com/docs/hooks/reference/, read 2026-10-26",
|
|
75
|
+
"delivery": {
|
|
76
|
+
"hookSpecificOutput": {
|
|
77
|
+
"additionalContext": "__SIMT_TEXT__"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"post": {
|
|
82
|
+
"event": "AfterTool",
|
|
83
|
+
"verified": "geminicli.com/docs/hooks/reference/, read 2026-10-26",
|
|
84
|
+
"delivery": {
|
|
85
|
+
"hookSpecificOutput": {
|
|
86
|
+
"additionalContext": "__SIMT_TEXT__"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"deny": {
|
|
91
|
+
"decision": "deny",
|
|
92
|
+
"reason": "__SIMT_TEXT__"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"payload": {
|
|
96
|
+
"toolKey": "tool_name",
|
|
97
|
+
"inputKey": "tool_input",
|
|
98
|
+
"shellTools": [
|
|
99
|
+
"run_shell_command"
|
|
100
|
+
],
|
|
101
|
+
"commandKey": "command",
|
|
102
|
+
"writeTools": [
|
|
103
|
+
"write_file",
|
|
104
|
+
"replace"
|
|
105
|
+
],
|
|
106
|
+
"pathKeys": [
|
|
107
|
+
"file_path"
|
|
108
|
+
],
|
|
109
|
+
"rootKeys": [
|
|
110
|
+
[
|
|
111
|
+
"cwd"
|
|
112
|
+
]
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
"pluginOrder": 2,
|
|
116
|
+
"plugin": {
|
|
117
|
+
"rootVar": "extensionPath",
|
|
118
|
+
"projectVar": null,
|
|
119
|
+
"hooksPath": "hooks/hooks.json",
|
|
120
|
+
"runtimeDir": "."
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "opencode",
|
|
3
|
+
"loaderOrder": 1,
|
|
4
|
+
"surfaceOrder": 1,
|
|
5
|
+
"surface": {
|
|
6
|
+
"id": "opencode",
|
|
7
|
+
"label": "opencode (AGENTS.md + .agents/skills)",
|
|
8
|
+
"readers": "OpenCode v2, and AGENTS.md is also read natively by Codex, Cursor, Windsurf, Zed, Amp, Jules, Copilot CLI and the DeepSeek harness; Claude Code falls back to it, Antigravity and Gemini CLI accept GEMINI.md as an equal name",
|
|
9
|
+
"primary": "AGENTS.md",
|
|
10
|
+
"kind": "region",
|
|
11
|
+
"rule": null,
|
|
12
|
+
"foldRules": true,
|
|
13
|
+
"readsAgentsMd": "all",
|
|
14
|
+
"configDirs": [
|
|
15
|
+
".opencode"
|
|
16
|
+
],
|
|
17
|
+
"skills": ".agents/skills",
|
|
18
|
+
"documentedSkillsPaths": [
|
|
19
|
+
".opencode/skills",
|
|
20
|
+
".claude/skills",
|
|
21
|
+
".agents/skills"
|
|
22
|
+
],
|
|
23
|
+
"readLimits": [
|
|
24
|
+
{
|
|
25
|
+
"reader": "Codex",
|
|
26
|
+
"bytes": 32768,
|
|
27
|
+
"verified": "Codex project_doc_max_bytes defaults to 32 KiB"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"verified": "OpenCode v2 instructions and skills docs (opencode.ai/v2/docs, read 2026-10-14) — AGENTS.md only, combined from the workspace up to the project root; CLAUDE.md is not a fallback; skills at .opencode/skills/, .claude/skills/ and .agents/skills/<name>/SKILL.md, with the model shown name and description",
|
|
31
|
+
"note": ".agents/skills is one of the three skill locations opencode documents, so the same directory serves the standard surface without a symlink."
|
|
32
|
+
},
|
|
33
|
+
"loader": {
|
|
34
|
+
"id": "opencode",
|
|
35
|
+
"label": "OpenCode v2",
|
|
36
|
+
"readers": "OpenCode v2",
|
|
37
|
+
"surface": "opencode",
|
|
38
|
+
"start": [
|
|
39
|
+
{
|
|
40
|
+
"scope": "project",
|
|
41
|
+
"path": "AGENTS.md",
|
|
42
|
+
"tree": "chain",
|
|
43
|
+
"atStart": true,
|
|
44
|
+
"why": "OpenCode v2 instructions docs: every AGENTS.md from the workspace directory up to the project root is loaded and combined; V2 recognizes AGENTS.md only and does not fall back to CLAUDE.md"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"scope": "user",
|
|
48
|
+
"path": "~/.config/opencode/AGENTS.md",
|
|
49
|
+
"tree": "single",
|
|
50
|
+
"atStart": true,
|
|
51
|
+
"why": "OpenCode v2 instructions docs: the global file is loaded first, before the project chain"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"scope": "project",
|
|
55
|
+
"path": ".claude/skills/{name}/SKILL.md",
|
|
56
|
+
"tree": "nested",
|
|
57
|
+
"dir": ".claude/skills",
|
|
58
|
+
"suffix": "/SKILL.md",
|
|
59
|
+
"atStart": true,
|
|
60
|
+
"metadata": true,
|
|
61
|
+
"why": "OpenCode v2 skills docs: .claude/skills is a project compatibility source, registered before .agents/skills"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"scope": "project",
|
|
65
|
+
"path": ".agents/skills/{name}/SKILL.md",
|
|
66
|
+
"tree": "nested",
|
|
67
|
+
"dir": ".agents/skills",
|
|
68
|
+
"suffix": "/SKILL.md",
|
|
69
|
+
"atStart": true,
|
|
70
|
+
"metadata": true,
|
|
71
|
+
"why": "OpenCode v2 skills docs: .agents/skills is a project compatibility source; the model is shown the id, name and description, not the body"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"scope": "project",
|
|
75
|
+
"path": ".opencode/skills/{name}/SKILL.md",
|
|
76
|
+
"tree": "nested",
|
|
77
|
+
"dir": ".opencode/skills",
|
|
78
|
+
"suffix": "/SKILL.md",
|
|
79
|
+
"atStart": true,
|
|
80
|
+
"metadata": true,
|
|
81
|
+
"why": "OpenCode v2 skills docs: .opencode/skills is the project source and outranks .agents/skills and .claude/skills for the same id"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"alsoReads": [
|
|
85
|
+
{
|
|
86
|
+
"what": "AGENTS.md below the workspace",
|
|
87
|
+
"atStart": false,
|
|
88
|
+
"why": "OpenCode v2 instructions docs: instruction files below the workspace load when the agent reads or lists that area, not at session start"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"what": "opencode.json `instructions` array",
|
|
92
|
+
"atStart": false,
|
|
93
|
+
"why": "OpenCode v2 instructions docs: the config schema accepts an instructions array, but V2 does not resolve its files, globs or URLs"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"order": "global ~/.config/opencode/AGENTS.md, then project AGENTS.md files from the workspace directory up to the project root (nearest first); files are combined, not overridden",
|
|
97
|
+
"cap": null,
|
|
98
|
+
"capNote": "no byte cap published for AGENTS.md; V2 accepts an instructions array and does not load it, so there is no fetch timeout to apply",
|
|
99
|
+
"imports": {
|
|
100
|
+
"syntax": null,
|
|
101
|
+
"depth": 0,
|
|
102
|
+
"why": "OpenCode v2 instructions docs: an AGENTS.md reference to another file is not an include; V2 loads AGENTS.md files by discovery, not by @path expansion"
|
|
103
|
+
},
|
|
104
|
+
"nested": "cumulative up to the project root at session start; files below the workspace load when that area is read",
|
|
105
|
+
"skills": [
|
|
106
|
+
".agents/skills",
|
|
107
|
+
".opencode/skills",
|
|
108
|
+
".claude/skills"
|
|
109
|
+
],
|
|
110
|
+
"hooks": {
|
|
111
|
+
"path": ".opencode/plugins/simt-dev.js",
|
|
112
|
+
"verified": "OpenCode v2 plugins docs: direct .ts and .js files and immediate plugin packages are loaded from every discovered .opencode/plugins/ directory, and the plugins array in opencode.json(c)"
|
|
113
|
+
},
|
|
114
|
+
"agents": {
|
|
115
|
+
"dir": ".opencode/agents",
|
|
116
|
+
"ext": ".md",
|
|
117
|
+
"verified": "OpenCode v2 agents docs (opencode.ai/v2/docs/agents, read 2026-10-30): subagents are markdown files with a description frontmatter under .opencode/agents/"
|
|
118
|
+
},
|
|
119
|
+
"mcp": {
|
|
120
|
+
"path": "opencode.json",
|
|
121
|
+
"key": "mcp.servers",
|
|
122
|
+
"verified": "OpenCode v2 MCP servers docs (read 2026-10-14): servers are named under mcp.servers; V2 does not place server names directly under mcp"
|
|
123
|
+
},
|
|
124
|
+
"verified": "OpenCode v2 instructions, skills, plugins and MCP docs (opencode.ai/v2/docs, read 2026-10-14) — V2 loads AGENTS.md only, combining the global file with every project AGENTS.md from the workspace up to the project root; CLAUDE.md is not a fallback; the instructions array is accepted and not loaded; skills advertise name and description from .opencode/skills, .claude/skills and .agents/skills",
|
|
125
|
+
"note": "This row is OpenCode v2. V1 read CLAUDE.md when AGENTS.md was absent and loaded the instructions array; V2 does neither, so a meter that still did would bill bytes this reader does not pay."
|
|
126
|
+
},
|
|
127
|
+
"hooks": {
|
|
128
|
+
"id": "opencode",
|
|
129
|
+
"event": "execute.before",
|
|
130
|
+
"matcher": "shell|write|edit",
|
|
131
|
+
"file": true,
|
|
132
|
+
"denyExit": 2,
|
|
133
|
+
"verified": "opencode.ai/v2/docs/build/plugins/server, read 2026-10-29",
|
|
134
|
+
"prompt": {
|
|
135
|
+
"event": "prompt",
|
|
136
|
+
"verified": "opencode.ai/v2/docs/build/plugins/server, read 2026-10-29",
|
|
137
|
+
"delivery": {
|
|
138
|
+
"text": "__SIMT_TEXT__"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"post": null,
|
|
142
|
+
"deny": {
|
|
143
|
+
"reason": "__SIMT_TEXT__"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"payload": {
|
|
147
|
+
"toolKey": "tool",
|
|
148
|
+
"inputKey": "input",
|
|
149
|
+
"shellTools": [
|
|
150
|
+
"shell"
|
|
151
|
+
],
|
|
152
|
+
"commandKey": "command",
|
|
153
|
+
"writeTools": [
|
|
154
|
+
"write",
|
|
155
|
+
"edit"
|
|
156
|
+
],
|
|
157
|
+
"pathKeys": [
|
|
158
|
+
"path"
|
|
159
|
+
],
|
|
160
|
+
"rootKeys": [
|
|
161
|
+
[
|
|
162
|
+
"cwd"
|
|
163
|
+
]
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"pluginOrder": 4,
|
|
167
|
+
"plugin": {
|
|
168
|
+
"rootVar": null,
|
|
169
|
+
"projectVar": null,
|
|
170
|
+
"hooksPath": "server.js",
|
|
171
|
+
"runtimeDir": "."
|
|
172
|
+
}
|
|
173
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simt-dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "An intelligence amplifier for AI coding agents: rules and dual gates adopted by one command, plus local retrieval, task briefs, benches and repo memory — deterministic by default, with local-model adapters that propose and never select.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Said Elimam",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"templates",
|
|
28
28
|
"agents",
|
|
29
29
|
"skills",
|
|
30
|
+
"ecosystems",
|
|
31
|
+
"harnesses",
|
|
30
32
|
"docs/decisions",
|
|
31
33
|
"CHANGELOG.md"
|
|
32
34
|
],
|
|
@@ -41,6 +41,14 @@ the list to reach it, and never split one decision into several questions to fit
|
|
|
41
41
|
- If that is not available, present numbered choices with `(recommended)` on the first option.
|
|
42
42
|
- Do not ask operational or procedural choices; apply the best documented option.
|
|
43
43
|
|
|
44
|
+
## Small work (no plan file)
|
|
45
|
+
|
|
46
|
+
When the whole of the work is small — fewer than three slices — do not create a plan file.
|
|
47
|
+
Plan it and implement it directly, in a single slice: state the slice and its acceptance
|
|
48
|
+
checks, resolve any material decisions, and build it. The `{{plansGlob}}` tracker table and
|
|
49
|
+
its `NNNN-slug.md` deliverable are for work that spans multiple slices; a single slice has
|
|
50
|
+
nothing to track. The slice-commit procedure still applies to the one commit it produces.
|
|
51
|
+
|
|
44
52
|
## Required First Turn
|
|
45
53
|
|
|
46
54
|
1. State the smallest independently valuable slice, the owning repo, the active plan
|
package/src/advice/advice.mjs
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
// records or a prices table price the tokens, total tokens otherwise. Ordering by a number
|
|
22
22
|
// that does not exist for any run would be nondeterminism, not economics.
|
|
23
23
|
import { classifyTask } from '../brief/kind.mjs';
|
|
24
|
-
import { computeRunCost, isRunResolved, loadPrices
|
|
25
|
-
import {
|
|
24
|
+
import { computeRunCost, isRunResolved, loadPrices } from '../measure/metrics.mjs';
|
|
25
|
+
import { loadRunRecords } from '../measure/records.mjs';
|
|
26
|
+
import { loadTasks } from '../measure/tasks.mjs';
|
|
26
27
|
import { CONFIG_DIR } from '../name.mjs';
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
import { CLI } from '../name.mjs';
|
|
50
50
|
import { LOADERS } from '../projection/loaders.mjs';
|
|
51
|
+
import { harnessHooks, harnessPayloads } from '../harness/records.mjs';
|
|
51
52
|
import { renderOpencodePlugin } from './opencode.mjs';
|
|
52
53
|
import { EVENTS } from './policy.mjs';
|
|
53
54
|
import { AGENT_HOOK_SHIM } from './shim.mjs';
|
|
@@ -183,188 +184,93 @@ function patchFiles(text) {
|
|
|
183
184
|
return files.filter((f) => f.path !== '');
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
post: {
|
|
218
|
-
event: 'PostToolUse',
|
|
219
|
-
matcher: 'Bash',
|
|
220
|
-
verified: 'code.claude.com/docs/en/hooks, read 2026-10-19',
|
|
221
|
-
context: (text) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: text } }),
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
codex: {
|
|
225
|
-
id: 'codex',
|
|
226
|
-
event: 'PreToolUse',
|
|
227
|
-
matcher: 'Bash|apply_patch',
|
|
228
|
-
denyExit: 2,
|
|
229
|
-
verified: 'developers.openai.com/codex/hooks, read 2026-10-16',
|
|
230
|
-
// Codex is the one harness that will not run a hook until a person has approved the
|
|
231
|
-
// exact definition, keyed by its hash — so an install that does not say this leaves
|
|
232
|
-
// the repository with a hook that never fires and no way to tell why.
|
|
233
|
-
trust: 'Codex skips a hook nobody has trusted: review it with /hooks, or the entry stays inert.',
|
|
234
|
-
root: (p) => p.cwd,
|
|
235
|
-
extract: (p) => ({
|
|
236
|
-
command: p.tool_name === 'Bash' ? (p.tool_input?.command ?? null) : null,
|
|
237
|
-
// apply_patch carries its targets inside the patch text, one header line per file;
|
|
238
|
-
// a move names its destination on the `+++` line after `*** Move File:`, and the
|
|
239
|
-
// destination is the file the move writes (review 0003 Q16).
|
|
240
|
-
writes:
|
|
241
|
-
p.tool_name === 'apply_patch'
|
|
242
|
-
? patchWrites(String(p.tool_input?.command ?? ''))
|
|
243
|
-
: [], // entries carry {path, patch} so the region check maps hunks (Q17)
|
|
244
|
-
}),
|
|
245
|
-
deny: (message) => ({
|
|
246
|
-
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: message },
|
|
247
|
-
}),
|
|
248
|
-
prompt: {
|
|
249
|
-
event: 'UserPromptSubmit',
|
|
250
|
-
verified: 'developers.openai.com/codex/hooks, read 2026-10-19',
|
|
251
|
-
context: (text) => ({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: text } }),
|
|
252
|
-
},
|
|
253
|
-
post: {
|
|
254
|
-
event: 'PostToolUse',
|
|
255
|
-
matcher: 'Bash',
|
|
256
|
-
verified: 'developers.openai.com/codex/hooks, read 2026-10-19',
|
|
257
|
-
context: (text) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: text } }),
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
cursor: {
|
|
261
|
-
id: 'cursor',
|
|
262
|
-
event: 'preToolUse',
|
|
263
|
-
matcher: 'Shell|Write|Edit',
|
|
264
|
-
denyExit: 2,
|
|
265
|
-
verified: 'cursor.com/docs/agent/hooks, read 2026-10-16',
|
|
266
|
-
root: (p) => p.working_directory ?? p.cwd,
|
|
267
|
-
extract: (p) => ({
|
|
268
|
-
command: p.tool_name === 'Shell' ? (p.tool_input?.command ?? null) : null,
|
|
269
|
-
writes: ['Write', 'Edit'].includes(p.tool_name)
|
|
270
|
-
? [writeOf(p.tool_input?.file_path ?? p.tool_input?.path, p.tool_input)].filter(Boolean)
|
|
271
|
-
: [],
|
|
272
|
-
}),
|
|
273
|
-
deny: (message) => ({ permission: 'deny', user_message: message, agent_message: message }),
|
|
274
|
-
// beforeSubmitPrompt is documented as continue/block and nothing else, so there is no
|
|
275
|
-
// key to deliver a packet in. Writing the entry anyway would put a command that reads
|
|
276
|
-
// a repository on the path of every prompt a person types, to say nothing.
|
|
277
|
-
prompt: null,
|
|
278
|
-
post: null,
|
|
279
|
-
},
|
|
280
|
-
antigravity: {
|
|
281
|
-
id: 'antigravity',
|
|
282
|
-
event: 'PreToolUse',
|
|
283
|
-
matcher: 'run_command|write_to_file|replace_file_content|multi_replace_file_content',
|
|
284
|
-
// The page documents the decision JSON and not an exit code, so the denial travels
|
|
285
|
-
// in the JSON only: a non-zero exit whose meaning the vendor has not stated is not
|
|
286
|
-
// a block, it is a guess that happens to be loud.
|
|
287
|
-
denyExit: 0,
|
|
288
|
-
verified: 'antigravity.google/docs/hooks, read 2026-10-16',
|
|
289
|
-
root: (p) => p.workspacePaths?.[0] ?? p.cwd,
|
|
290
|
-
extract: (p) => {
|
|
291
|
-
const call = p.toolCall ?? {};
|
|
292
|
-
const args = call.args ?? {};
|
|
293
|
-
return {
|
|
294
|
-
command: call.name === 'run_command' ? (args.CommandLine ?? null) : null,
|
|
295
|
-
writes: ['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(call.name)
|
|
296
|
-
? [writeOf(args.TargetFile, args)].filter(Boolean)
|
|
297
|
-
: [], // CodeContent/old/new travel with the path for the region check (Q17)
|
|
298
|
-
};
|
|
299
|
-
},
|
|
300
|
-
deny: (message) => ({ decision: 'deny', reason: message }),
|
|
301
|
-
// No prompt event is documented for this harness's hooks file, and a guessed event
|
|
302
|
-
// name is a hook that never fires while its config says it does.
|
|
303
|
-
prompt: null,
|
|
304
|
-
post: null,
|
|
305
|
-
},
|
|
306
|
-
gemini: {
|
|
307
|
-
id: 'gemini',
|
|
308
|
-
event: 'BeforeTool',
|
|
309
|
-
matcher: 'run_shell_command|write_file|replace',
|
|
310
|
-
// The vendor's timeout unit is milliseconds (default 60000), not the seconds every
|
|
311
|
-
// other row uses; a bare 10 here would be a ten-millisecond budget, which is a hook
|
|
312
|
-
// that never finishes. The row carries its own value so the shared constant stays
|
|
313
|
-
// honest for the four vendors that measure in seconds.
|
|
314
|
-
timeout: 10000,
|
|
315
|
-
// The reference documents the decision JSON and an exit-2 system block; the denial
|
|
316
|
-
// travels in the JSON, as antigravity does.
|
|
317
|
-
denyExit: 0,
|
|
318
|
-
verified: 'geminicli.com/docs/hooks/reference/, read 2026-10-26',
|
|
319
|
-
root: (p) => p.cwd,
|
|
320
|
-
extract: (p) => ({
|
|
321
|
-
command: p.tool_name === 'run_shell_command' ? (p.tool_input?.command ?? null) : null,
|
|
322
|
-
writes: ['write_file', 'replace'].includes(p.tool_name)
|
|
323
|
-
? [writeOf(p.tool_input?.file_path, p.tool_input)].filter(Boolean)
|
|
187
|
+
// The payload a hook reads: where the tool name and its input sit, and the keys that
|
|
188
|
+
// name a command and a file. A record cannot carry a function, so the engine reads the
|
|
189
|
+
// record's keys and applies them: a harness that puts a field somewhere new is a new
|
|
190
|
+
// record, not a new branch here (Q71).
|
|
191
|
+
function pick(obj, keys) {
|
|
192
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
193
|
+
for (const k of keys) {
|
|
194
|
+
const v = obj[k];
|
|
195
|
+
if (v !== undefined && v !== null) return v;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function extractOf(spec) {
|
|
201
|
+
return (p) => {
|
|
202
|
+
const payload = p ?? {};
|
|
203
|
+
// antigravity nests the call under `toolCall`; the rest name the tool at the top
|
|
204
|
+
// level. `toolNameKey` is present only on the nested shape, so its presence is
|
|
205
|
+
// the detector.
|
|
206
|
+
const nested = spec.toolNameKey !== undefined;
|
|
207
|
+
const call = nested ? (payload[spec.toolKey] ?? {}) : null;
|
|
208
|
+
const toolName = nested ? call[spec.toolNameKey] : payload[spec.toolKey];
|
|
209
|
+
const input = nested ? (call[spec.inputSubKey] ?? {}) : (payload[spec.inputKey] ?? {});
|
|
210
|
+
return {
|
|
211
|
+
command: (spec.shellTools ?? []).includes(toolName) ? (input[spec.commandKey] ?? null) : null,
|
|
212
|
+
// apply_patch carries its targets inside the patch text (writesFrom: "patch");
|
|
213
|
+
// every other write tool names its file through the path keys.
|
|
214
|
+
writes: (spec.writeTools ?? []).includes(toolName)
|
|
215
|
+
? spec.writesFrom === 'patch'
|
|
216
|
+
? patchWrites(String(input[spec.commandKey] ?? ''))
|
|
217
|
+
: [writeOf(pick(input, spec.pathOverrides?.[toolName] ?? spec.pathKeys), input)].filter(Boolean)
|
|
324
218
|
: [],
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The record's `rootKeys`: a list of accessor paths, first non-null wins. The caller
|
|
224
|
+
// applies its `?? process.cwd()` floor (factsFrom).
|
|
225
|
+
function rootOf(spec) {
|
|
226
|
+
return (p) => {
|
|
227
|
+
for (const path of spec.rootKeys ?? []) {
|
|
228
|
+
let v = p;
|
|
229
|
+
for (const seg of path) v = v?.[seg];
|
|
230
|
+
if (v !== undefined && v !== null) return v;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// The `__SIMT_TEXT__` sentinel in a record's deny and delivery templates: the text a
|
|
237
|
+
// decision or a delivery carries is substituted for it, and JSON.stringify downstream
|
|
238
|
+
// escapes whatever it carries.
|
|
239
|
+
const TEXT = '__SIMT_TEXT__';
|
|
240
|
+
|
|
241
|
+
function applyText(value, text) {
|
|
242
|
+
if (typeof value === 'string') return value === TEXT ? text : value;
|
|
243
|
+
if (Array.isArray(value)) return value.map((v) => applyText(v, text));
|
|
244
|
+
if (value && typeof value === 'object') {
|
|
245
|
+
const out = {};
|
|
246
|
+
for (const [k, v] of Object.entries(value)) out[k] = applyText(v, text);
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// The hooks dialect per harness, as records (Q71): the data a config document and a
|
|
253
|
+
// decision are shaped by, with the page each shape was read from. What a record cannot
|
|
254
|
+
// carry is attached here — `root` and `extract` read the record's payload keys, and
|
|
255
|
+
// `deny`, `prompt.context` and `post.context` apply its templates. Module-internal on
|
|
256
|
+
// purpose: `adapters()` and `adapter()` are the only doors in, and both check the
|
|
257
|
+
// reader table, so an id with no hooks file is no adapter.
|
|
258
|
+
const HOOKS = harnessHooks();
|
|
259
|
+
const PAYLOADS = harnessPayloads();
|
|
260
|
+
|
|
261
|
+
const ADAPTERS = Object.fromEntries(
|
|
262
|
+
Object.entries(HOOKS).map(([id, h]) => [
|
|
263
|
+
id,
|
|
264
|
+
{
|
|
265
|
+
...h,
|
|
266
|
+
root: rootOf(PAYLOADS[id]),
|
|
267
|
+
extract: extractOf(PAYLOADS[id]),
|
|
268
|
+
deny: (message) => applyText(h.deny, message),
|
|
269
|
+
...(h.prompt ? { prompt: { ...h.prompt, context: (text) => applyText(h.prompt.delivery, text) } } : {}),
|
|
270
|
+
...(h.post ? { post: { ...h.post, context: (text) => applyText(h.post.delivery, text) } } : {}),
|
|
364
271
|
},
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
};
|
|
272
|
+
]),
|
|
273
|
+
);
|
|
368
274
|
|
|
369
275
|
/** The six adapters, in the order the reader table lists them. */
|
|
370
276
|
export function adapters() {
|