@andares/pi-toolkits 0.9.8 → 0.11.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/README.md +117 -5
- package/README.zh-CN.md +102 -5
- package/package.json +8 -3
- package/src/features/init/analyze.ts +162 -0
- package/src/features/init/apply.test.ts +264 -0
- package/src/features/init/apply.ts +693 -0
- package/src/features/init/audit.test.ts +292 -0
- package/src/features/init/audit.ts +586 -0
- package/src/features/init/coverage.test.ts +93 -0
- package/src/features/init/coverage.ts +163 -0
- package/src/features/init/detect.test.ts +237 -0
- package/src/features/init/detect.ts +808 -0
- package/src/features/init/dispatch.ts +135 -0
- package/src/features/init/index.test.ts +331 -0
- package/src/features/init/index.ts +569 -0
- package/src/features/init/plan.test.ts +261 -0
- package/src/features/init/plan.ts +867 -0
- package/src/features/init/render.ts +131 -0
- package/src/features/init/spec.ts +429 -0
- package/src/features/init/templates/agents-skeleton.md +44 -0
- package/src/features/init/templates/memory-readme.md +99 -0
- package/src/features/init/templates/signpost.md +15 -0
- package/src/features/init/templates/state-gitignore +6 -0
- package/src/features/init/templates.test.ts +58 -0
- package/src/features/init/templates.ts +79 -0
- package/src/features/init/test-helpers.ts +89 -0
- package/src/features/init/tool.ts +72 -0
- package/src/features/init/types.ts +284 -0
- package/src/features/init/ui.ts +160 -0
- package/src/features/permission/audit.test.ts +159 -0
- package/src/features/permission/audit.ts +145 -0
- package/src/features/permission/index.test.ts +138 -3
- package/src/features/permission/index.ts +108 -12
- package/src/features/permission/policy.ts +39 -3
- package/src/index.ts +9 -2
- package/src/lib/approval-log.test.ts +289 -0
- package/src/lib/approval-log.ts +724 -0
- package/src/lib/gates.audit.test.ts +343 -0
- package/src/lib/gates.ts +291 -10
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Agent memory framework v{{frameworkVersion}}
|
|
2
|
+
|
|
3
|
+
This repository keeps agent memory in a fixed shape so that every session can find
|
|
4
|
+
a fact in one place instead of re-deriving it, and so the memory does not grow into
|
|
5
|
+
an unreadable pile. Read this file **before writing memory**; it is the rulebook,
|
|
6
|
+
while `AGENTS.md` at the repository root is the entry point and index.
|
|
7
|
+
|
|
8
|
+
## Where things live
|
|
9
|
+
|
|
10
|
+
| Location | Answers | Lifetime |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| `specs/PLAN.md` | What are we going to do, what is still open | rewritten as work moves |
|
|
13
|
+
| `specs/<unit>.md` | One unit of work: requirement, approach, acceptance | written before work, status updated at close |
|
|
14
|
+
| `.agents/memory/` | What is known, decided, and why | long-lived; append and compact only |
|
|
15
|
+
| `.agents/implementations/` | What was changed, when, with what evidence | one record per change, never rewritten |
|
|
16
|
+
| `.agents/prompts/` | The operator's own words (optional) | read-only input |
|
|
17
|
+
|
|
18
|
+
## Memory files
|
|
19
|
+
|
|
20
|
+
| File | Owns | Never holds |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `FINISHED.md` | Closed work: summary + evidence pointer + historical appendix | spec bodies, progress lists |
|
|
23
|
+
| `FUTURE.md` | Deferred, rejected, waiting-for-a-trigger | schedule promises |
|
|
24
|
+
| `ARCHITECTURE.md` | Layering, version baseline, decision records, investigation archive | visual specs, progress |
|
|
25
|
+
| `DESIGN.md` | Visual single source of truth: tokens, palette, spacing, component looks | implementation details |
|
|
26
|
+
| `TERMS.md` | Glossary and the rules that tell look-alike terms apart | visual specs |
|
|
27
|
+
| `REVIEW.md` | Review ledger: judgement, trade-off, destination | a second copy of a spec |
|
|
28
|
+
| `DEBUGGING.md` | Runtime diagnosis runbook | code facts |
|
|
29
|
+
| `FUNCTION-<MODULE>.md` | One module's functional rules | copies of another module's rules |
|
|
30
|
+
|
|
31
|
+
No file is created before it has content. Files not listed here are not memory.
|
|
32
|
+
|
|
33
|
+
## Write rules
|
|
34
|
+
|
|
35
|
+
1. **One fact, one home.** Detail lives in the file that owns it; everywhere else
|
|
36
|
+
is a pointer (`path#section`). If you are about to restate a fact, write a
|
|
37
|
+
pointer instead.
|
|
38
|
+
2. **AGENTS.md holds only what every session needs**: invariants, commands,
|
|
39
|
+
routing, the index. Anything that can be looked up does not belong there.
|
|
40
|
+
3. **Record conclusions and constraints, not narration.** No chat transcripts, no
|
|
41
|
+
"first I did X, then Y".
|
|
42
|
+
4. **Append and compact; never rewrite history to look better.** Change records
|
|
43
|
+
are written once. The single exception: a directory move may update the path
|
|
44
|
+
strings inside them, and that must be listed in the migration report.
|
|
45
|
+
5. **Date every stale-able claim** (versions, measurements, "currently"). An
|
|
46
|
+
unmeasured claim is `inference` or `TBD`, never a fact.
|
|
47
|
+
6. **Keep the operator's decisions verbatim** (quote + date + who). They are the
|
|
48
|
+
most expensive thing to reconstruct.
|
|
49
|
+
7. **Progress lives in the todo tool.** No second progress table in any document.
|
|
50
|
+
8. **Specs precede work; records follow it.** Close a spec honestly (status line);
|
|
51
|
+
never delete it — archive it and leave a link when superseded.
|
|
52
|
+
9. **Compress before you append.** Over budget means compact first, then add.
|
|
53
|
+
10. **No secrets, credentials, or personal paths** in memory. Evidence you would
|
|
54
|
+
not publish does not belong here.
|
|
55
|
+
|
|
56
|
+
## Budgets
|
|
57
|
+
|
|
58
|
+
| What | Limit | Why |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `AGENTS.md` | soft {{softBudget}} chars, hard {{hardBudget}} | paid on **every** request |
|
|
61
|
+
| one memory file | {{memoryBudget}} chars | navigability, not tokens |
|
|
62
|
+
| change records | {{implementationsBudget}} files | roll up and archive past this |
|
|
63
|
+
|
|
64
|
+
The audit reports overruns. Overrunning is not an emergency; growing without a
|
|
65
|
+
compaction plan is.
|
|
66
|
+
|
|
67
|
+
## Compaction recipes
|
|
68
|
+
|
|
69
|
+
| File | How to shrink it |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `specs/PLAN.md` | Move closed items to `FINISHED.md` as a 3-8 line summary + pointer |
|
|
72
|
+
| `FINISHED.md` | Keep milestone summaries + pointers; move whole eras to `.agents/memory/archive/<year>/` |
|
|
73
|
+
| `FUTURE.md` | Every entry needs a trigger; adopted → `specs/PLAN.md` + one-line pointer; unreachable → mark dropped with a date |
|
|
74
|
+
| `ARCHITECTURE.md` | Decisions are append-only (context / decision / consequence / date); supersede, do not delete |
|
|
75
|
+
| `FUNCTION-<MODULE>.md` | Split by sub-domain past budget; move dating measurements into a measurement appendix |
|
|
76
|
+
| `REVIEW.md` | One section per round; the overview table is the only summary; fold old rounds into an appendix |
|
|
77
|
+
| change records | Roll up per milestone into `FINISHED.md`, then move originals to `.agents/implementations/archive/<year>/` |
|
|
78
|
+
|
|
79
|
+
## Audit
|
|
80
|
+
|
|
81
|
+
`/ask-init check` (or the `framework_audit` tool during an init run) reports:
|
|
82
|
+
|
|
83
|
+
- structure: missing files, index ↔ file mismatches, `AGENTS.override.md` shadowing
|
|
84
|
+
- paths: references to `memory` or `implementations` without the `.agents/` prefix (a move would break them)
|
|
85
|
+
- duplication: the same section heading in two files (one fact, two homes)
|
|
86
|
+
- order: sections appended after the historical appendix
|
|
87
|
+
- budgets: `AGENTS.md` and memory-file sizes
|
|
88
|
+
- state: `PLAN.md` ↔ specs drift, stale `todos/`, `FUTURE.md` entries with no trigger
|
|
89
|
+
- evidence: `FINISHED.md` entries without a change-record pointer
|
|
90
|
+
|
|
91
|
+
Codes are stable (`E-…`, `W-…`, `I-…`), so reports can be compared across runs.
|
|
92
|
+
|
|
93
|
+
## Upgrading the framework
|
|
94
|
+
|
|
95
|
+
`.agents/framework/state.toml` records the framework version. When this file's
|
|
96
|
+
version is newer than the state file, `/ask-init` plans an upgrade: deterministic
|
|
97
|
+
steps run in code, prose changes are handed to the model as tasks. A project whose
|
|
98
|
+
state version is newer than the installed tool is never migrated — upgrade the
|
|
99
|
+
tool instead.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# `.agents/` — agent-facing material
|
|
2
|
+
|
|
3
|
+
Everything in this directory exists for the agent, not for the application: it is
|
|
4
|
+
committed (except the runtime folders ignored by `framework/.gitignore`) and
|
|
5
|
+
reviewed by humans, but it is not part of the shipped product.
|
|
6
|
+
|
|
7
|
+
| Directory | What it is |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `.agents/memory/` | Long-lived memory: decisions, constraints, finished work, deferred ideas. Rules: `.agents/memory/README.md` |
|
|
10
|
+
| `.agents/implementations/` | One record per change (what changed, why, evidence) |
|
|
11
|
+
| `.agents/prompts/` | The operator's own words, verbatim (optional; often local-only) |
|
|
12
|
+
| `.agents/framework/` | Tool state for `/ask-init`: `state.toml`, snapshots, plans, reports |
|
|
13
|
+
|
|
14
|
+
The entry point every session reads is `AGENTS.md` in the repository root.
|
|
15
|
+
Memory is not loaded automatically; it is read on demand from the index there.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { countBareRefs } from "./detect.js";
|
|
3
|
+
import { renderTemplate, templateNames } from "./templates.js";
|
|
4
|
+
|
|
5
|
+
const TOKENS: Record<string, string> = {
|
|
6
|
+
project: "demo",
|
|
7
|
+
date: "2026-10-26",
|
|
8
|
+
contract: "> Memory contract (agent-framework v1).",
|
|
9
|
+
docTitle: "project guide (AGENTS.md)",
|
|
10
|
+
softBudget: "12000",
|
|
11
|
+
hardBudget: "20000",
|
|
12
|
+
memoryBudget: "80000",
|
|
13
|
+
implementationsBudget: "200",
|
|
14
|
+
index: "| File | Owns | Read when |\n| --- | --- | --- |\n| `specs/PLAN.md` | route | before work |",
|
|
15
|
+
roots: "",
|
|
16
|
+
frameworkVersion: "1",
|
|
17
|
+
statePath: ".agents/framework/state.toml",
|
|
18
|
+
contractPath: ".agents/memory/README.md",
|
|
19
|
+
implementationsDir: ".agents/implementations",
|
|
20
|
+
h_what: "What this is",
|
|
21
|
+
h_rules: "Hard rules",
|
|
22
|
+
h_commands: "Commands",
|
|
23
|
+
h_layout: "Layout",
|
|
24
|
+
h_index: "Plan and memory index",
|
|
25
|
+
h_impact: "Change impact index",
|
|
26
|
+
h_roots: "Workspace roots",
|
|
27
|
+
h_terms: "Terms",
|
|
28
|
+
h_maintenance: "Framework maintenance",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("templates", () => {
|
|
32
|
+
it("renders every shipped template without leaving placeholders", () => {
|
|
33
|
+
for (const name of templateNames()) {
|
|
34
|
+
const text = renderTemplate(name, TOKENS, "en");
|
|
35
|
+
expect(text, name).not.toMatch(/\{\{/);
|
|
36
|
+
expect(text.length, name).toBeGreaterThan(20);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("renders the localized seeds", () => {
|
|
41
|
+
expect(renderTemplate("seed-finished", TOKENS, "zh")).toContain("# 已完成");
|
|
42
|
+
expect(renderTemplate("plan", TOKENS, "zh")).toContain("# 计划");
|
|
43
|
+
expect(renderTemplate("seed-architecture", TOKENS, "en")).toContain("# Architecture");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("keeps the shipped contract free of legacy path references", () => {
|
|
47
|
+
for (const name of ["memory-readme", "signpost", "agents-skeleton"]) {
|
|
48
|
+
const text = renderTemplate(name, TOKENS, "en");
|
|
49
|
+
expect(countBareRefs(text, "memory/"), name).toBe(0);
|
|
50
|
+
expect(countBareRefs(text, "implementations/"), name).toBe(0);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("fails loudly instead of writing an unresolved placeholder", () => {
|
|
55
|
+
expect(() => renderTemplate("agents-skeleton", {}, "en")).toThrow(/no value for/);
|
|
56
|
+
expect(() => renderTemplate("nope", TOKENS, "en")).toThrow(/unknown template/);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* init feature — template loading and substitution.
|
|
3
|
+
*
|
|
4
|
+
* Templates fall into two groups:
|
|
5
|
+
* - files under `templates/` (shipped with the package, path-resolved from
|
|
6
|
+
* `import.meta.url` so a global install works);
|
|
7
|
+
* - seeds that live in code because they are localized (`SEEDS`, `PLAN_SEED`).
|
|
8
|
+
*
|
|
9
|
+
* A rendered template must never leak `{{token}}` syntax into a project, so the
|
|
10
|
+
* loader fails loudly on an unresolved placeholder instead of writing it.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import {
|
|
14
|
+
PLAN_SEED,
|
|
15
|
+
SEEDS,
|
|
16
|
+
type ProjectLanguage,
|
|
17
|
+
} from "./spec.js";
|
|
18
|
+
|
|
19
|
+
const FILE_TEMPLATES = new Set([
|
|
20
|
+
"agents-skeleton",
|
|
21
|
+
"memory-readme",
|
|
22
|
+
"signpost",
|
|
23
|
+
"state-gitignore",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/** Templates whose content is not Markdown (written verbatim). */
|
|
27
|
+
const RAW_TEMPLATES = new Set(["state-gitignore"]);
|
|
28
|
+
|
|
29
|
+
const SEED_TEMPLATES = new Set([
|
|
30
|
+
"seed-finished",
|
|
31
|
+
"seed-future",
|
|
32
|
+
"seed-architecture",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export function templateNames(): string[] {
|
|
36
|
+
return [...FILE_TEMPLATES, ...SEED_TEMPLATES, "plan"];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Collapse runs of blank lines left behind by empty optional sections. */
|
|
40
|
+
function tidy(text: string): string {
|
|
41
|
+
return `${text.replace(/\n{3,}/g, "\n\n").replace(/[ \t]+\n/g, "\n").trimEnd()}\n`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function renderTemplate(
|
|
45
|
+
name: string,
|
|
46
|
+
tokens: Record<string, string>,
|
|
47
|
+
language: ProjectLanguage,
|
|
48
|
+
): string {
|
|
49
|
+
let body: string;
|
|
50
|
+
if (name === "plan") {
|
|
51
|
+
body = PLAN_SEED[language];
|
|
52
|
+
} else if (name.startsWith("seed-")) {
|
|
53
|
+
const role = name.slice("seed-".length);
|
|
54
|
+
body = SEEDS[language][role] ?? "";
|
|
55
|
+
if (body.length === 0) throw new Error(`unknown seed template: ${name}`);
|
|
56
|
+
} else if (FILE_TEMPLATES.has(name)) {
|
|
57
|
+
// `.gitignore` content is not Markdown; keep it extension-less so Markdown
|
|
58
|
+
// linters do not read its `#` comments as headings.
|
|
59
|
+
const suffix = RAW_TEMPLATES.has(name) ? "" : ".md";
|
|
60
|
+
body = readFileSync(new URL(`./templates/${name}${suffix}`, import.meta.url), "utf8");
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`unknown template "${name}" (known: ${templateNames().join(", ")})`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const rendered = body.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_match, key: string) => {
|
|
68
|
+
const value = tokens[key];
|
|
69
|
+
if (value === undefined) {
|
|
70
|
+
throw new Error(`template "${name}" has no value for {{${key}}}`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (/\{\{/.test(rendered)) {
|
|
76
|
+
throw new Error(`template "${name}" left a placeholder unresolved`);
|
|
77
|
+
}
|
|
78
|
+
return tidy(rendered);
|
|
79
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for the init feature. Kept in `src/` (like the tests themselves)
|
|
3
|
+
* so the published package and the test run see the same files; nothing here is
|
|
4
|
+
* imported by the extension at runtime.
|
|
5
|
+
*/
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
|
|
11
|
+
/** Create a throw-away project from a path → content map. */
|
|
12
|
+
export function project(files: Record<string, string>): string {
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "pi-init-"));
|
|
14
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
15
|
+
const absolute = join(root, rel);
|
|
16
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
17
|
+
writeFileSync(absolute, content, "utf8");
|
|
18
|
+
}
|
|
19
|
+
return root;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function remove(root: string): void {
|
|
23
|
+
rmSync(root, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `git init` + add + commit, for tests that need tracked files. */
|
|
27
|
+
export function gitProject(files: Record<string, string>, message = "init"): string {
|
|
28
|
+
const root = project(files);
|
|
29
|
+
execFileSync("git", ["-C", root, "init", "-q"], { stdio: "ignore" });
|
|
30
|
+
execFileSync("git", ["-C", root, "add", "-A"], { stdio: "ignore" });
|
|
31
|
+
execFileSync(
|
|
32
|
+
"git",
|
|
33
|
+
[
|
|
34
|
+
"-C",
|
|
35
|
+
root,
|
|
36
|
+
"-c",
|
|
37
|
+
"user.email=test@example.com",
|
|
38
|
+
"-c",
|
|
39
|
+
"user.name=test",
|
|
40
|
+
"commit",
|
|
41
|
+
"-q",
|
|
42
|
+
"-m",
|
|
43
|
+
message,
|
|
44
|
+
],
|
|
45
|
+
{ stdio: "ignore" },
|
|
46
|
+
);
|
|
47
|
+
return root;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** An installed-looking framework (state + contract + canonical dirs). */
|
|
51
|
+
export function installedProject(extra: Record<string, string> = {}): string {
|
|
52
|
+
return project({
|
|
53
|
+
"AGENTS.md": [
|
|
54
|
+
"# demo — project guide",
|
|
55
|
+
"",
|
|
56
|
+
"> Memory contract (agent-framework v1): one fact, one home.",
|
|
57
|
+
"",
|
|
58
|
+
"## 1. What this is",
|
|
59
|
+
"",
|
|
60
|
+
"A demo project.",
|
|
61
|
+
"",
|
|
62
|
+
"## 5. Plan and memory index",
|
|
63
|
+
"",
|
|
64
|
+
"| File | Owns | Read when |",
|
|
65
|
+
"| --- | --- | --- |",
|
|
66
|
+
"| `specs/PLAN.md` | route | before work |",
|
|
67
|
+
"| `.agents/memory/FINISHED.md` | closed work | closing |",
|
|
68
|
+
"",
|
|
69
|
+
].join("\n"),
|
|
70
|
+
"specs/PLAN.md": "# Plan (open work)\n\n## Route\n\n_None._\n",
|
|
71
|
+
".agents/memory/README.md": "# Agent memory framework v1\n\nRules.\n",
|
|
72
|
+
".agents/memory/FINISHED.md": "# Finished work\n\n## Summary\n\n_None._\n",
|
|
73
|
+
".agents/memory/FUTURE.md": "# Future and deferred\n\n_None._\n",
|
|
74
|
+
".agents/memory/ARCHITECTURE.md": "# Architecture\n\n## Layering\n\n_None._\n",
|
|
75
|
+
".agents/implementations/2026-01-01_init.md":
|
|
76
|
+
"# init\n\n完成时间:2026-01-01 00:00:00\n\n## 范围\n\nInitial.\n",
|
|
77
|
+
".agents/framework/state.toml": [
|
|
78
|
+
"version = 1",
|
|
79
|
+
'tool = "0.0.0-test"',
|
|
80
|
+
'created = "2026-01-01T00:00:00+08:00"',
|
|
81
|
+
'updated = "2026-01-01T00:00:00+08:00"',
|
|
82
|
+
'profile = "core"',
|
|
83
|
+
"modules = []",
|
|
84
|
+
'lang = "en"',
|
|
85
|
+
"",
|
|
86
|
+
].join("\n"),
|
|
87
|
+
...extra,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* init feature — the `framework_audit` tool.
|
|
3
|
+
*
|
|
4
|
+
* Registered inactive and activated for the duration of an init run, so the
|
|
5
|
+
* model can close its own loop: edit → audit → fix the `E-*` findings and the
|
|
6
|
+
* open coverage rows before reporting. Outside such a run it stays out of the
|
|
7
|
+
* tool list; the human entry point is `/ask-init check`.
|
|
8
|
+
*
|
|
9
|
+
* Read-only: it never writes, so it is safe to call at any point.
|
|
10
|
+
*/
|
|
11
|
+
import { Type } from "typebox";
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { detect } from "./detect.js";
|
|
14
|
+
import { audit, countFindings, renderFindings } from "./audit.js";
|
|
15
|
+
import { FRAMEWORK_VERSION } from "./spec.js";
|
|
16
|
+
|
|
17
|
+
export const FRAMEWORK_AUDIT_TOOL = "framework_audit";
|
|
18
|
+
|
|
19
|
+
const PARAMS = Type.Object({});
|
|
20
|
+
|
|
21
|
+
const DESCRIPTION = `Audit this repository's agent-memory framework (read-only).
|
|
22
|
+
|
|
23
|
+
Checks the layout under .agents/ and specs/, budgets, duplicated section headings,
|
|
24
|
+
appendix ordering, bare path references, plan/spec drift, stale zero-byte files and
|
|
25
|
+
the migration coverage table. Findings carry stable codes: E-* must be fixed, W-* are
|
|
26
|
+
warnings that should either be fixed or recorded in specs/PLAN.md, I-* are informational.
|
|
27
|
+
|
|
28
|
+
Use it after writing or moving memory files, and before reporting an init/migration
|
|
29
|
+
run as complete.`;
|
|
30
|
+
|
|
31
|
+
const GUIDELINES = [
|
|
32
|
+
"After any init or migration edit, run `framework_audit` and fix every E-* finding and every OPEN coverage row before reporting completion.",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export function registerAuditTool(pi: ExtensionAPI): void {
|
|
36
|
+
pi.registerTool<typeof PARAMS, { errors: number; warnings: number; infos: number }>({
|
|
37
|
+
name: FRAMEWORK_AUDIT_TOOL,
|
|
38
|
+
label: "Framework audit",
|
|
39
|
+
description: DESCRIPTION,
|
|
40
|
+
promptSnippet: "Audit the agent-memory framework layout (read-only)",
|
|
41
|
+
promptGuidelines: GUIDELINES,
|
|
42
|
+
parameters: PARAMS,
|
|
43
|
+
defaultActive: false,
|
|
44
|
+
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
45
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
46
|
+
try {
|
|
47
|
+
const facts = detect(ctx.cwd);
|
|
48
|
+
const findings = audit(facts);
|
|
49
|
+
const counts = countFindings(findings);
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: "text" as const,
|
|
54
|
+
text: renderFindings(findings, {
|
|
55
|
+
root: ctx.cwd,
|
|
56
|
+
frameworkVersion: FRAMEWORK_VERSION,
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
details: counts,
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text" as const, text: `framework_audit: ${message}` }],
|
|
66
|
+
details: { errors: 1, warnings: 0, infos: 0 },
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|