agent-method 1.5.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 +256 -0
- package/bin/agent-method.js +58 -0
- package/docs/internal/feature-registry.yaml +1532 -0
- package/lib/cli/check.js +71 -0
- package/lib/cli/helpers.js +151 -0
- package/lib/cli/init.js +60 -0
- package/lib/cli/pipeline.js +163 -0
- package/lib/cli/refine.js +202 -0
- package/lib/cli/route.js +62 -0
- package/lib/cli/scan.js +28 -0
- package/lib/cli/status.js +61 -0
- package/lib/cli/upgrade.js +146 -0
- package/lib/init.js +240 -0
- package/lib/pipeline.js +887 -0
- package/lib/registry.js +108 -0
- package/package.json +39 -0
- package/templates/README.md +293 -0
- package/templates/entry-points/.cursorrules +109 -0
- package/templates/entry-points/AGENT.md +109 -0
- package/templates/entry-points/CLAUDE.md +109 -0
- package/templates/extensions/MANIFEST.md +110 -0
- package/templates/extensions/analytical-system.md +96 -0
- package/templates/extensions/code-project.md +77 -0
- package/templates/extensions/data-exploration.md +117 -0
- package/templates/full/.context/BASE.md +68 -0
- package/templates/full/.context/COMPOSITION.md +47 -0
- package/templates/full/.context/METHODOLOGY.md +84 -0
- package/templates/full/.context/REGISTRY.md +75 -0
- package/templates/full/.cursorrules +128 -0
- package/templates/full/AGENT.md +128 -0
- package/templates/full/CLAUDE.md +128 -0
- package/templates/full/PLAN.md +67 -0
- package/templates/full/PROJECT-PROFILE.md +61 -0
- package/templates/full/PROJECT.md +46 -0
- package/templates/full/REQUIREMENTS.md +30 -0
- package/templates/full/ROADMAP.md +39 -0
- package/templates/full/SESSION-LOG.md +41 -0
- package/templates/full/STATE.md +42 -0
- package/templates/full/SUMMARY.md +24 -0
- package/templates/full/docs/index.md +46 -0
- package/templates/full/todos/backlog.md +19 -0
- package/templates/starter/.context/BASE.md +66 -0
- package/templates/starter/.context/METHODOLOGY.md +70 -0
- package/templates/starter/.cursorrules +113 -0
- package/templates/starter/AGENT.md +113 -0
- package/templates/starter/CLAUDE.md +113 -0
- package/templates/starter/PLAN.md +67 -0
- package/templates/starter/PROJECT-PROFILE.md +44 -0
- package/templates/starter/PROJECT.md +46 -0
- package/templates/starter/ROADMAP.md +39 -0
- package/templates/starter/SESSION-LOG.md +41 -0
- package/templates/starter/STATE.md +42 -0
package/lib/cli/check.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** agent-method check — validate entry point and project setup. */
|
|
2
|
+
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
findEntryPoint,
|
|
6
|
+
resolveProjectType,
|
|
7
|
+
getPipeline,
|
|
8
|
+
loadRegistryData,
|
|
9
|
+
} from "./helpers.js";
|
|
10
|
+
|
|
11
|
+
export function register(program) {
|
|
12
|
+
program
|
|
13
|
+
.command("check [entry-point]")
|
|
14
|
+
.description("Validate your entry point and project setup")
|
|
15
|
+
.option(
|
|
16
|
+
"-p, --project-type <type>",
|
|
17
|
+
"Project type: code, context, data, mix, general (auto-detected if omitted)"
|
|
18
|
+
)
|
|
19
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
20
|
+
.option("--json", "Output as JSON")
|
|
21
|
+
.action(async (entryPoint, opts) => {
|
|
22
|
+
const { validateEntryPoint, detectProjectType } = await getPipeline();
|
|
23
|
+
const reg = await loadRegistryData(opts.registry);
|
|
24
|
+
|
|
25
|
+
if (!entryPoint) {
|
|
26
|
+
entryPoint = findEntryPoint(".");
|
|
27
|
+
if (!entryPoint) {
|
|
28
|
+
console.error(
|
|
29
|
+
"No entry point found in current directory " +
|
|
30
|
+
"(looked for CLAUDE.md, .cursorrules, AGENT.md).\n" +
|
|
31
|
+
"Specify a path: agent-method check path/to/CLAUDE.md"
|
|
32
|
+
);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let projectType;
|
|
38
|
+
if (opts.projectType) {
|
|
39
|
+
projectType = resolveProjectType(opts.projectType);
|
|
40
|
+
} else {
|
|
41
|
+
const detected = detectProjectType(dirname(resolve(entryPoint)));
|
|
42
|
+
projectType = detected.project_type || "general";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = validateEntryPoint(entryPoint, projectType, reg);
|
|
46
|
+
console.log(`Checking: ${entryPoint} (type: ${projectType})`);
|
|
47
|
+
|
|
48
|
+
if (opts.json) {
|
|
49
|
+
console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
} else {
|
|
51
|
+
const overall = result.valid ? "PASS" : "FAIL";
|
|
52
|
+
console.log(` Overall: ${overall}`);
|
|
53
|
+
for (const [checkId, chk] of Object.entries(result.checks)) {
|
|
54
|
+
const cStatus = chk.pass ? "PASS" : "FAIL";
|
|
55
|
+
let extra = "";
|
|
56
|
+
if (chk.missing && chk.missing.length > 0) {
|
|
57
|
+
extra = ` (missing: ${chk.missing.join(", ")})`;
|
|
58
|
+
}
|
|
59
|
+
console.log(` ${checkId}: ${cStatus}${extra}`);
|
|
60
|
+
}
|
|
61
|
+
if (result.issues && result.issues.length > 0) {
|
|
62
|
+
console.log("\n Issues:");
|
|
63
|
+
for (const issue of result.issues) {
|
|
64
|
+
console.log(
|
|
65
|
+
` [${issue.severity}] ${issue.check}: ${issue.description}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CLI helpers — project type resolution, registry loading,
|
|
3
|
+
* entry point discovery, output formatting.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
7
|
+
import { resolve, join, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Package metadata
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
const _pkg = JSON.parse(
|
|
18
|
+
readFileSync(resolve(__dirname, "..", "..", "package.json"), "utf-8")
|
|
19
|
+
);
|
|
20
|
+
export const pkg = _pkg;
|
|
21
|
+
|
|
22
|
+
/** Package root directory (two levels up from lib/cli/). */
|
|
23
|
+
export const packageRoot = resolve(__dirname, "..", "..");
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Project type aliases
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export const PROJECT_TYPE_ALIASES = {
|
|
30
|
+
context: "analytical",
|
|
31
|
+
mix: "mixed",
|
|
32
|
+
all: "mixed",
|
|
33
|
+
code: "code",
|
|
34
|
+
data: "data",
|
|
35
|
+
analytical: "analytical",
|
|
36
|
+
mixed: "mixed",
|
|
37
|
+
general: "general",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const VALID_TYPE_NAMES = Object.keys(PROJECT_TYPE_ALIASES).sort();
|
|
41
|
+
|
|
42
|
+
export function resolveProjectType(name) {
|
|
43
|
+
const resolved = PROJECT_TYPE_ALIASES[name.toLowerCase()];
|
|
44
|
+
if (!resolved) {
|
|
45
|
+
console.error(
|
|
46
|
+
`Unknown project type: '${name}'\nValid types: ${VALID_TYPE_NAMES.join(", ")}`
|
|
47
|
+
);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Lazy module loading
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
let _registryModule = null;
|
|
58
|
+
let _pipelineModule = null;
|
|
59
|
+
|
|
60
|
+
export async function getRegistry() {
|
|
61
|
+
if (!_registryModule) {
|
|
62
|
+
_registryModule = await import("../registry.js");
|
|
63
|
+
}
|
|
64
|
+
return _registryModule;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getPipeline() {
|
|
68
|
+
if (!_pipelineModule) {
|
|
69
|
+
_pipelineModule = await import("../pipeline.js");
|
|
70
|
+
}
|
|
71
|
+
return _pipelineModule;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function loadRegistryData(registryPath) {
|
|
75
|
+
const { loadRegistry } = await getRegistry();
|
|
76
|
+
try {
|
|
77
|
+
return loadRegistry(registryPath || undefined);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error(`Error: ${e.message}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// File discovery
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
export function findEntryPoint(directory) {
|
|
89
|
+
const d = resolve(directory);
|
|
90
|
+
for (const name of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
|
|
91
|
+
const p = join(d, name);
|
|
92
|
+
if (existsSync(p)) return p;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function findSessionLog(directory = ".") {
|
|
98
|
+
const d = resolve(directory);
|
|
99
|
+
for (const name of ["SESSION-LOG.md", "session-log.md"]) {
|
|
100
|
+
const p = join(d, name);
|
|
101
|
+
if (existsSync(p)) return p;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function readMethodVersion(entryPointPath) {
|
|
107
|
+
const content = readFileSync(entryPointPath, "utf-8");
|
|
108
|
+
const m = content.match(/method_version:\s*(\S+)/);
|
|
109
|
+
return m ? m[1] : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function basename_of(filepath) {
|
|
113
|
+
return filepath.split(/[\\/]/).pop();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Output formatting
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
function formatValue(value) {
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
if (value.length === 0) return "[]";
|
|
123
|
+
if (typeof value[0] === "object") {
|
|
124
|
+
return value.map((v) => JSON.stringify(v)).join(", ");
|
|
125
|
+
}
|
|
126
|
+
return `[${value.join(", ")}]`;
|
|
127
|
+
}
|
|
128
|
+
if (value && typeof value === "object") {
|
|
129
|
+
return JSON.stringify(value);
|
|
130
|
+
}
|
|
131
|
+
return String(value);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function outputData(data, asJson) {
|
|
135
|
+
if (asJson) {
|
|
136
|
+
console.log(JSON.stringify(data, null, 2));
|
|
137
|
+
} else {
|
|
138
|
+
for (const [key, value] of Object.entries(data)) {
|
|
139
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
140
|
+
console.log(` ${key}:`);
|
|
141
|
+
for (const [k2, v2] of Object.entries(value)) {
|
|
142
|
+
console.log(` ${k2}: ${formatValue(v2)}`);
|
|
143
|
+
}
|
|
144
|
+
} else if (Array.isArray(value)) {
|
|
145
|
+
console.log(` ${key}: ${formatValue(value)}`);
|
|
146
|
+
} else {
|
|
147
|
+
console.log(` ${key}: ${value}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
package/lib/cli/init.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** agent-method init — set up a new project or describe entry point contents. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
resolveProjectType,
|
|
5
|
+
getPipeline,
|
|
6
|
+
loadRegistryData,
|
|
7
|
+
outputData,
|
|
8
|
+
PROJECT_TYPE_ALIASES,
|
|
9
|
+
} from "./helpers.js";
|
|
10
|
+
|
|
11
|
+
export function register(program) {
|
|
12
|
+
program
|
|
13
|
+
.command("init <project-type> [directory]")
|
|
14
|
+
.description("Set up a new project with methodology templates")
|
|
15
|
+
.option("--tier <tier>", "Template tier (starter/full)", "starter")
|
|
16
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
17
|
+
.option("--json", "Output as JSON")
|
|
18
|
+
.option(
|
|
19
|
+
"--describe",
|
|
20
|
+
"Only describe what the entry point should contain (no file creation)"
|
|
21
|
+
)
|
|
22
|
+
.action(async (projectTypeArg, directory, opts) => {
|
|
23
|
+
const projectType = resolveProjectType(projectTypeArg);
|
|
24
|
+
|
|
25
|
+
if (opts.describe || !directory) {
|
|
26
|
+
// Describe mode
|
|
27
|
+
const { generateEntryPoint } = await getPipeline();
|
|
28
|
+
const reg = await loadRegistryData(opts.registry);
|
|
29
|
+
const result = generateEntryPoint(projectType, opts.tier, reg);
|
|
30
|
+
|
|
31
|
+
// Show friendly name
|
|
32
|
+
let friendly = projectType;
|
|
33
|
+
for (const [alias, internal] of Object.entries(PROJECT_TYPE_ALIASES)) {
|
|
34
|
+
if (internal === projectType && alias !== projectType) {
|
|
35
|
+
friendly = `${alias} (${projectType})`;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(
|
|
41
|
+
`Entry point specification for: ${friendly} (${opts.tier})`
|
|
42
|
+
);
|
|
43
|
+
outputData(result, opts.json);
|
|
44
|
+
if (!directory) {
|
|
45
|
+
console.log(
|
|
46
|
+
"\nTo create a project, specify a directory:\n" +
|
|
47
|
+
` agent-method init ${projectTypeArg} ~/my-project`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Interactive setup mode
|
|
54
|
+
const { initProject } = await import("../init.js");
|
|
55
|
+
await initProject(projectType, directory, {
|
|
56
|
+
tier: opts.tier,
|
|
57
|
+
registryPath: opts.registry,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** agent-method pipeline — advanced subcommands for debugging and testing. */
|
|
2
|
+
|
|
3
|
+
import { resolve, dirname, join } from "node:path";
|
|
4
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
+
import {
|
|
6
|
+
resolveProjectType,
|
|
7
|
+
getPipeline,
|
|
8
|
+
getRegistry,
|
|
9
|
+
loadRegistryData,
|
|
10
|
+
outputData,
|
|
11
|
+
} from "./helpers.js";
|
|
12
|
+
|
|
13
|
+
export function register(program) {
|
|
14
|
+
const pipeline = program
|
|
15
|
+
.command("pipeline")
|
|
16
|
+
.description(
|
|
17
|
+
"Advanced pipeline commands for debugging and testing.\n" +
|
|
18
|
+
"Most users should use check, scan, route instead."
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
pipeline
|
|
22
|
+
.command("classify <query>")
|
|
23
|
+
.description("Classify a natural language query (S1)")
|
|
24
|
+
.option(
|
|
25
|
+
"-p, --project-type <type>",
|
|
26
|
+
"Project type: code, context, data, mix, general",
|
|
27
|
+
"general"
|
|
28
|
+
)
|
|
29
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
30
|
+
.option("--json", "Output as JSON")
|
|
31
|
+
.action(async (query, opts) => {
|
|
32
|
+
const { classify } = await getPipeline();
|
|
33
|
+
const reg = await loadRegistryData(opts.registry);
|
|
34
|
+
const projectType = resolveProjectType(opts.projectType);
|
|
35
|
+
const result = classify(query, projectType, reg);
|
|
36
|
+
console.log(`Query: "${query}"`);
|
|
37
|
+
outputData(result, opts.json);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
pipeline
|
|
41
|
+
.command("select <query-type> <project-type>")
|
|
42
|
+
.description("Select workflow for a query type (S2)")
|
|
43
|
+
.option("--first-session", "Force WF-04 bootstrap workflow")
|
|
44
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
45
|
+
.option("--json", "Output as JSON")
|
|
46
|
+
.action(async (queryType, projectType, opts) => {
|
|
47
|
+
const { selectWorkflow } = await getPipeline();
|
|
48
|
+
await loadRegistryData(opts.registry);
|
|
49
|
+
const result = selectWorkflow(
|
|
50
|
+
queryType,
|
|
51
|
+
projectType,
|
|
52
|
+
opts.firstSession || false
|
|
53
|
+
);
|
|
54
|
+
outputData(result, opts.json);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
pipeline
|
|
58
|
+
.command("resolve <workflow-id> <stage>")
|
|
59
|
+
.description("Resolve features for a workflow stage (S3)")
|
|
60
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
61
|
+
.option("--json", "Output as JSON")
|
|
62
|
+
.action(async (workflowId, stage, opts) => {
|
|
63
|
+
const { resolveFeatures } = await getPipeline();
|
|
64
|
+
const reg = await loadRegistryData(opts.registry);
|
|
65
|
+
const result = resolveFeatures(workflowId, stage, reg);
|
|
66
|
+
console.log(`Workflow: ${workflowId} | Stage: ${stage}`);
|
|
67
|
+
outputData(result, opts.json);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
pipeline
|
|
71
|
+
.command("cascade <trigger>")
|
|
72
|
+
.description("Compute cascade chain from a trigger (S5)")
|
|
73
|
+
.option(
|
|
74
|
+
"--context <context>",
|
|
75
|
+
"Cascade context: code, context, data, mix, universal",
|
|
76
|
+
"universal"
|
|
77
|
+
)
|
|
78
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
79
|
+
.option("--json", "Output as JSON")
|
|
80
|
+
.action(async (trigger, opts) => {
|
|
81
|
+
const { resolveCascade } = await getPipeline();
|
|
82
|
+
await loadRegistryData(opts.registry);
|
|
83
|
+
const context =
|
|
84
|
+
opts.context !== "universal"
|
|
85
|
+
? resolveProjectType(opts.context)
|
|
86
|
+
: opts.context;
|
|
87
|
+
const result = resolveCascade([], trigger, context);
|
|
88
|
+
outputData(result, opts.json);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
pipeline
|
|
92
|
+
.command("test [fixtures...]")
|
|
93
|
+
.description("Run YAML test fixtures against the pipeline")
|
|
94
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
95
|
+
.action(async (fixtures, opts) => {
|
|
96
|
+
const { runFixture } = await getPipeline();
|
|
97
|
+
const { findRegistry } = await getRegistry();
|
|
98
|
+
const reg = await loadRegistryData(opts.registry);
|
|
99
|
+
|
|
100
|
+
let files;
|
|
101
|
+
if (fixtures && fixtures.length > 0) {
|
|
102
|
+
files = fixtures.map((f) => resolve(f));
|
|
103
|
+
} else {
|
|
104
|
+
let regPath;
|
|
105
|
+
try {
|
|
106
|
+
regPath = opts.registry ? resolve(opts.registry) : findRegistry();
|
|
107
|
+
} catch {
|
|
108
|
+
regPath = ".";
|
|
109
|
+
}
|
|
110
|
+
const fixtureDir = resolve(
|
|
111
|
+
dirname(regPath), "..", "..", "tests", "fixtures"
|
|
112
|
+
);
|
|
113
|
+
const altDir = resolve(
|
|
114
|
+
dirname(new URL(import.meta.url).pathname), "..", "..", "tests", "fixtures"
|
|
115
|
+
);
|
|
116
|
+
const searchDir = existsSync(fixtureDir) ? fixtureDir : altDir;
|
|
117
|
+
|
|
118
|
+
if (existsSync(searchDir)) {
|
|
119
|
+
files = readdirSync(searchDir)
|
|
120
|
+
.filter((f) => f.startsWith("pipeline-") && f.endsWith(".yaml"))
|
|
121
|
+
.sort()
|
|
122
|
+
.map((f) => join(searchDir, f));
|
|
123
|
+
} else {
|
|
124
|
+
files = [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (files.length === 0) {
|
|
129
|
+
console.error("No fixture files found.");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let totalPassed = 0;
|
|
134
|
+
let totalFailed = 0;
|
|
135
|
+
for (const fpath of files) {
|
|
136
|
+
if (!existsSync(fpath)) {
|
|
137
|
+
console.log(` Fixture not found: ${fpath}`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const result = runFixture(fpath, reg);
|
|
141
|
+
const fileStatus = result.failed === 0 ? "PASS" : "FAIL";
|
|
142
|
+
console.log(
|
|
143
|
+
` ${result.stage.padEnd(12)} ${result.passed}/${result.total} passed [${fileStatus}]`
|
|
144
|
+
);
|
|
145
|
+
totalPassed += result.passed;
|
|
146
|
+
totalFailed += result.failed;
|
|
147
|
+
|
|
148
|
+
for (const detail of result.details) {
|
|
149
|
+
if (detail.status === "FAIL") {
|
|
150
|
+
console.log(` FAIL ${detail.id}: ${detail.description}`);
|
|
151
|
+
if (detail.actual) {
|
|
152
|
+
console.log(` actual: ${JSON.stringify(detail.actual)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(
|
|
159
|
+
`\n Total: ${totalPassed} passed, ${totalFailed} failed, ${totalPassed + totalFailed} total`
|
|
160
|
+
);
|
|
161
|
+
process.exit(totalFailed > 0 ? 1 : 0);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/** agent-method refine — extract refinement report from session history. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync, existsSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { findSessionLog } from "./helpers.js";
|
|
5
|
+
|
|
6
|
+
export function register(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("refine [session-log]")
|
|
9
|
+
.description("Extract a refinement report from session history")
|
|
10
|
+
.option("-o, --output <path>", "Output file (default: stdout)")
|
|
11
|
+
.option("--json", "Output as JSON")
|
|
12
|
+
.action(async (sessionLog, opts) => {
|
|
13
|
+
if (!sessionLog) {
|
|
14
|
+
sessionLog = findSessionLog(".");
|
|
15
|
+
if (!sessionLog) {
|
|
16
|
+
console.error(
|
|
17
|
+
"No SESSION-LOG.md found in current directory.\n" +
|
|
18
|
+
"Specify a path: agent-method refine path/to/SESSION-LOG.md"
|
|
19
|
+
);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!existsSync(sessionLog)) {
|
|
25
|
+
console.error(`File not found: ${sessionLog}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const content = readFileSync(sessionLog, "utf-8");
|
|
30
|
+
const parsed = parseSessionLog(content);
|
|
31
|
+
|
|
32
|
+
let result;
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
result = JSON.stringify(parsed, null, 2);
|
|
35
|
+
} else {
|
|
36
|
+
result = generateRefinementReport(parsed);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (opts.output) {
|
|
40
|
+
writeFileSync(opts.output, result, "utf-8");
|
|
41
|
+
console.log(
|
|
42
|
+
`Refinement report written to ${opts.output} (${parsed.entries.length} sessions)`
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(result);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Session log parsing
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
function parseSessionLog(content) {
|
|
55
|
+
const context = {};
|
|
56
|
+
const ctxMatch = content.match(
|
|
57
|
+
/## Project context\s*\n\s*\|[^\n]+\n\s*\|[-| ]+\n((?:\|[^\n]+\n)*)/
|
|
58
|
+
);
|
|
59
|
+
if (ctxMatch) {
|
|
60
|
+
for (const row of ctxMatch[1].trim().split("\n")) {
|
|
61
|
+
const cols = row
|
|
62
|
+
.split("|")
|
|
63
|
+
.map((c) => c.trim())
|
|
64
|
+
.filter((c) => c);
|
|
65
|
+
if (cols.length >= 2) {
|
|
66
|
+
context[cols[0].toLowerCase()] = cols[1];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const entries = [];
|
|
72
|
+
const entryPattern =
|
|
73
|
+
/###\s+S(\d+)\s*(?:\u2014|--)\s*(\S+)\s*(?:\u2014|--)\s*(.+?)$(.*?)(?=###\s+S\d+|$)/gms;
|
|
74
|
+
let m;
|
|
75
|
+
while ((m = entryPattern.exec(content)) !== null) {
|
|
76
|
+
const [, num, date, title, body] = m;
|
|
77
|
+
const entry = { session: parseInt(num, 10), date, title: title.trim() };
|
|
78
|
+
|
|
79
|
+
for (const line of body.split("\n")) {
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
82
|
+
const segments = trimmed.split("|").map((s) => s.trim());
|
|
83
|
+
for (const seg of segments) {
|
|
84
|
+
if (seg.includes(":")) {
|
|
85
|
+
const idx = seg.indexOf(":");
|
|
86
|
+
const key = seg.slice(0, idx).trim().toLowerCase();
|
|
87
|
+
const val = seg.slice(idx + 1).trim();
|
|
88
|
+
if (
|
|
89
|
+
[
|
|
90
|
+
"model", "profile", "workflow", "queries",
|
|
91
|
+
"features", "cascades", "decisions", "friction", "finding",
|
|
92
|
+
].includes(key)
|
|
93
|
+
) {
|
|
94
|
+
entry[key] = val;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
entries.push(entry);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { context, entries };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function generateRefinementReport(parsed) {
|
|
106
|
+
const ctx = parsed.context;
|
|
107
|
+
const entries = parsed.entries;
|
|
108
|
+
|
|
109
|
+
const project = ctx["project name"] || "Unknown";
|
|
110
|
+
const ptype = ctx["project type"] || "unknown";
|
|
111
|
+
const profile = ctx["integration profile"] || "unknown";
|
|
112
|
+
const extensions = ctx["extension(s)"] || "none";
|
|
113
|
+
|
|
114
|
+
const workflows = new Set();
|
|
115
|
+
let totalFriction = 0;
|
|
116
|
+
let totalFindings = 0;
|
|
117
|
+
const findingsList = [];
|
|
118
|
+
const frictionList = [];
|
|
119
|
+
|
|
120
|
+
for (const e of entries) {
|
|
121
|
+
if (e.workflow) workflows.add(e.workflow);
|
|
122
|
+
const friction = e.friction || "none";
|
|
123
|
+
if (friction.toLowerCase() !== "none" && friction) {
|
|
124
|
+
totalFriction++;
|
|
125
|
+
frictionList.push(`S${e.session} (${e.date}): ${friction}`);
|
|
126
|
+
}
|
|
127
|
+
const finding = e.finding || "none";
|
|
128
|
+
if (finding.toLowerCase() !== "none" && finding) {
|
|
129
|
+
totalFindings++;
|
|
130
|
+
findingsList.push(`S${e.session} (${e.date}): ${finding}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const wfSorted = [...workflows].sort().join(", ") || "none";
|
|
135
|
+
const lines = [
|
|
136
|
+
`# Refinement Report: ${project}`,
|
|
137
|
+
"",
|
|
138
|
+
"Auto-generated from SESSION-LOG.md by `agent-method refine`.",
|
|
139
|
+
"",
|
|
140
|
+
"## Source",
|
|
141
|
+
"",
|
|
142
|
+
"| Field | Value |",
|
|
143
|
+
"|-------|-------|",
|
|
144
|
+
`| Project type | ${ptype} |`,
|
|
145
|
+
`| Extension(s) | ${extensions} |`,
|
|
146
|
+
`| Workflow(s) exercised | ${wfSorted} |`,
|
|
147
|
+
`| Integration profile | ${profile} |`,
|
|
148
|
+
`| Sessions | ${entries.length} |`,
|
|
149
|
+
"",
|
|
150
|
+
"## Session summary",
|
|
151
|
+
"",
|
|
152
|
+
"| # | Date | Title | Workflow | Friction | Finding |",
|
|
153
|
+
"|---|------|-------|----------|----------|---------|",
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
for (const e of entries) {
|
|
157
|
+
const friction = e.friction || "none";
|
|
158
|
+
const finding = e.finding || "none";
|
|
159
|
+
const wf = e.workflow || "\u2014";
|
|
160
|
+
lines.push(
|
|
161
|
+
`| S${e.session} | ${e.date} | ${e.title} | ${wf} | ${friction} | ${finding} |`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lines.push(
|
|
166
|
+
"", "## Statistics", "",
|
|
167
|
+
`- **Total sessions**: ${entries.length}`,
|
|
168
|
+
`- **Workflows used**: ${wfSorted}`,
|
|
169
|
+
`- **Sessions with friction**: ${totalFriction}`,
|
|
170
|
+
`- **Sessions with findings**: ${totalFindings}`,
|
|
171
|
+
""
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (frictionList.length > 0) {
|
|
175
|
+
lines.push("## Friction points", "");
|
|
176
|
+
for (const f of frictionList) lines.push(`- ${f}`);
|
|
177
|
+
lines.push("");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (findingsList.length > 0) {
|
|
181
|
+
lines.push("## Findings", "");
|
|
182
|
+
for (const f of findingsList) lines.push(`- ${f}`);
|
|
183
|
+
lines.push("");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
lines.push(
|
|
187
|
+
"## Dimension scores", "",
|
|
188
|
+
"Fill in after reviewing the session data above:", "",
|
|
189
|
+
"| Dimension | Score | One-line summary |",
|
|
190
|
+
"|-----------|:-----:|-----------------|",
|
|
191
|
+
"| Context quality | \u2014 | |",
|
|
192
|
+
"| Decision preservation | \u2014 | |",
|
|
193
|
+
"| Scope discipline | \u2014 | |",
|
|
194
|
+
"| Cascade coverage | \u2014 | |",
|
|
195
|
+
"| Audit completeness | \u2014 | |",
|
|
196
|
+
"| Bootstrap speed | \u2014 | |",
|
|
197
|
+
"| Lifecycle fit | \u2014 | |",
|
|
198
|
+
"| Model adequacy | \u2014 | |"
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return lines.join("\n") + "\n";
|
|
202
|
+
}
|