Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
title: Discover project capability candidates
status: planned
status: implemented
summary: Add a CLI command that asks a coding agent to investigate an installed
project's full codebase and propose the first draft of what the app currently
does.
Expand Down Expand Up @@ -67,6 +67,11 @@ agent:
- core/project/initialize-capability-project
- core/model/define-capability-format
- core/agents/run-external-agent-cli
implementation:
references:
- packages/core/src/discovery.ts
- packages/core/tests/discovery.test.ts
- packages/cli/src/index.ts
verification:
automated:
- id: discovery-report-tests
Expand All @@ -84,62 +89,3 @@ agent:
- Initial agent discovery may miss behavior that is only visible through
runtime state, production configuration, external services, or code
paths outside the inspected scope.
review:
depth: partial
source: coding-agent
gaps:
- Provides a CLI command that runs from the target project root.
- Invokes or prepares a handoff for the user's selected coding agent to
inspect source code, tests, routes, handlers, UI flows, data models,
scripts, configuration, and documentation.
- Makes README files, package metadata, and docs supporting context, not
the sole or primary basis for capability discovery.
- Requires the agent to propose candidate capability titles, likely areas,
source evidence, and confidence notes from concrete code discovery.
- Produces a structured discovery report that records what files and
project areas the agent inspected.
- Does not create or overwrite capability files unless the user explicitly
requests output generation.
- Labels low-confidence candidates, shallow inspection, and missing code
evidence as review gaps.
intent_summary: Discover project capability candidates is currently planned. I
found no implementation references to inspect, so the acceptance criteria
are not implemented yet.
criteria:
- criterion: Provides a CLI command that runs from the target project root.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Invokes or prepares a handoff for the user's selected coding agent to
inspect source code, tests, routes, handlers, UI flows, data models,
scripts, configuration, and documentation.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Makes README files, package metadata, and docs supporting context,
not the sole or primary basis for capability discovery.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Requires the agent to propose candidate capability titles, likely
areas, source evidence, and confidence notes from concrete code
discovery.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Produces a structured discovery report that records what files and
project areas the agent inspected.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Does not create or overwrite capability files unless the user
explicitly requests output generation.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Labels low-confidence candidates, shallow inspection, and missing
code evidence as review gaps.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
done: false
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
title: Organize discovered capability map
status: planned
status: implemented
summary: Create a readable folder and dependency structure from agent-discovered
capabilities so users can browse what the app currently does.
intent: Make the generated `.capabilities/` tree feel like a product map rather
Expand Down Expand Up @@ -50,6 +50,10 @@ agent:
- core/discovery/generate-draft-capability-files
- core/graph/compile-capabilities
- core/graph/analyze-capability-impact
implementation:
references:
- packages/core/src/discovery.ts
- packages/core/tests/discoveryOrganization.test.ts
verification:
automated:
- id: capability-map-organization-tests
Expand All @@ -65,45 +69,3 @@ agent:
gaps:
- Dependency inference may need project-specific review for cross-cutting
infrastructure and shared services.
review:
depth: partial
source: coding-agent
gaps:
- Groups agent-generated capabilities into meaningful area and subarea
folders.
- Suggests capability IDs and filenames that are stable, readable, and
aligned with the generated folder structure.
- Detects likely dependencies between discovered capabilities when code
evidence or workflow relationships support them.
- Provides a summary index of generated areas and top-level capabilities.
- Flags ambiguous grouping decisions for human review instead of hiding
them.
intent_summary: Organize discovered capability map is currently planned. I found
no implementation references to inspect, so the acceptance criteria are
not implemented yet.
criteria:
- criterion: Groups agent-generated capabilities into meaningful area and subarea
folders.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Suggests capability IDs and filenames that are stable, readable, and
aligned with the generated folder structure.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Detects likely dependencies between discovered capabilities when code
evidence or workflow relationships support them.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Provides a summary index of generated areas and top-level capabilities.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
- criterion: Flags ambiguous grouping decisions for human review instead of hiding
them.
status: uncovered
notes: Capability status is planned and no agent.implementation.references are
declared.
done: false
161 changes: 160 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
analyzeCapabilityImpact,
adviseImplementationCoverage,
assessImplementationCoverage,
buildCapabilityDiscoveryPrompt,
buildAgentReviewPrompt,
buildAgentTaskBundle,
compileCapabilities,
Expand All @@ -17,18 +18,20 @@ import {
formatAssessmentAdviceReport,
formatImplementationCoverageReport,
loadCapabilities,
organizeDiscoveredCapabilityMap,
runExternalAgentCommand,
saveAgentReviewResult,
summarizeSavedReviewHealth,
summarizeCapabilityStatus,
syncReviewEvidence,
validateAgentReviewResult,
validateDiscoveryReport,
validateLoadedCapabilities,
writeCompiledCapabilities,
formatSyncReviewEvidenceReport,
formatCapabilities
} from "@capabilitykit/core";
import type { Capability, LoadCapabilitiesResult, VerificationGap } from "@capabilitykit/core";
import type { Capability, CapabilityDiscoveryReport, LoadCapabilitiesResult, VerificationGap } from "@capabilitykit/core";
import { installCapabilityKitSkill } from "./skillInstall.js";
import { filterStatusReportByRelease, formatStoryMapStatusReport, formatStoryMapViewerHtml } from "./statusOutput.js";

Expand Down Expand Up @@ -142,6 +145,65 @@ function collectOption(value: string, previous: string[] = []): string[] {
return [...previous, value];
}

function extractJsonObject(value: string): string {
const trimmed = value.trim();
if (trimmed.startsWith("{")) {
return trimmed;
}

const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
if (fenced?.[1]?.trim().startsWith("{")) {
return fenced[1].trim();
}

const first = trimmed.indexOf("{");
const last = trimmed.lastIndexOf("}");
if (first >= 0 && last > first) {
return trimmed.slice(first, last + 1);
}

throw new Error("Could not find a JSON discovery report in agent output.");
}

function parseDiscoveryReport(value: string): CapabilityDiscoveryReport {
const parsed = JSON.parse(extractJsonObject(value)) as CapabilityDiscoveryReport;
return parsed;
}

function formatDiscoveryValidation(report: CapabilityDiscoveryReport): string {
const validation = validateDiscoveryReport(report);
const organized = organizeDiscoveredCapabilityMap(report);
const lines = [
"CapabilityKit discovery report",
"",
`Candidates: ${report.candidates?.length ?? 0}`,
`Inspected files: ${report.inspected_files?.length ?? 0}`,
`Inspected areas: ${report.inspected_areas?.length ?? 0}`,
`Validation: ${validation.valid ? "valid" : "invalid"}`,
organized.summary
];

if (validation.issues.length > 0) {
lines.push("", "Issues:", ...validation.issues.map((issue) => ` - ${issue.message}`));
}

if (validation.gaps.length > 0) {
lines.push("", "Review gaps:", ...validation.gaps.map((gap) => ` - ${gap.message}`));
}

if (organized.areas.length > 0) {
lines.push("", "Suggested structure:");
for (const area of organized.areas) {
lines.push(` ${area.area}`);
for (const capability of area.capabilities) {
lines.push(` - ${capability.id} -> ${capability.filePath}`);
}
}
}

return `${lines.join("\n")}\n`;
}

type AdviceReport = Awaited<ReturnType<typeof adviseImplementationCoverage>>;

function noisyScore(capability: AdviceReport["capabilities"][number]): number {
Expand Down Expand Up @@ -1627,13 +1689,15 @@ Common workflows:
capabilitykit check Run the cheap daily health check
capabilitykit check --fix Format capabilities and refresh compiled output
capabilitykit next Show the next most useful maintenance actions
capabilitykit discover Prepare a coding-agent discovery prompt
capabilitykit verify <id> Save deterministic implementation review evidence
capabilitykit verify <id> --agent codex
Run an opt-in semantic review with an external agent

Command groups:
Setup: init, create, skill
Daily: check, next, format, validate, compile, status
Discovery: discover
Review: verify, assess, advise, review, sync-review, review-noisy
Agent: agent-task, agent-run, agent-review, review-result
Visualize: graph, graph-viewer, story-map-viewer
Expand Down Expand Up @@ -1715,6 +1779,101 @@ program
console.log(" Ask Codex: review this capability against its agent.implementation.references");
});


program
.command("discover")
.description("Ask or prepare a coding agent to discover capability candidates from this codebase")
.option("--command <command>", "external agent executable to run")
.option("--arg <value>", "argument to pass to the external agent command; repeat for multiple args", collectOption, [])
.option("--handoff <strategy>", "agent handoff strategy: stdin, argument, or prompt-file", "stdin")
.option("--prompt-file <path>", "prompt file path for prompt-file handoff")
.option("--output-prompt <path>", "write the discovery prompt to a file without running an agent")
.option("--report <path>", "write the parsed discovery report JSON to a file")
.option("--transcript <path>", "write stdout, stderr, exit code, and handoff details to a transcript file")
.option("--dry-run", "detect the command and prepare handoff files without running the external agent")
.option("--json", "print the parsed report and organized suggestions as JSON")
.action(
async (options: {
command?: string;
arg: string[];
handoff: string;
promptFile?: string;
outputPrompt?: string;
report?: string;
transcript?: string;
dryRun?: boolean;
json?: boolean;
}) => {
const prompt = buildCapabilityDiscoveryPrompt(process.cwd());

if (options.outputPrompt) {
const outputPath = path.resolve(process.cwd(), options.outputPrompt);
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, prompt);
console.log(`Wrote ${path.relative(process.cwd(), outputPath)}`);
return;
}

if (!options.command) {
console.log(prompt);
return;
}

const result = await runExternalAgentCommand({
command: options.command,
args: options.arg,
cwd: process.cwd(),
input: prompt,
handoff: parseAgentHandoff(options.handoff),
promptFilePath: options.promptFile,
transcriptPath: options.transcript,
dryRun: options.dryRun
});

console.log(`Command: ${[result.command, ...result.args].join(" ")}`);
console.log(`Handoff: ${result.handoff}`);
if (result.promptFilePath) {
console.log(`Prompt file: ${path.relative(process.cwd(), result.promptFilePath)}`);
}
if (result.transcriptPath) {
console.log(`Transcript: ${path.relative(process.cwd(), result.transcriptPath)}`);
}
if (result.dryRun) {
console.log("Result: dry run");
return;
}
console.log(`Exit code: ${result.exitCode ?? "unknown"}`);

if (result.exitCode !== 0) {
if (result.stdout.trim()) console.log(result.stdout.trimEnd());
if (result.stderr.trim()) console.error(result.stderr.trimEnd());
process.exitCode = result.exitCode ?? 1;
return;
}

const report = parseDiscoveryReport(result.stdout);
const validation = validateDiscoveryReport(report);
const organized = organizeDiscoveredCapabilityMap(report);

if (options.report) {
const reportPath = path.resolve(process.cwd(), options.report);
await fs.mkdir(path.dirname(reportPath), { recursive: true });
await fs.writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
}

if (options.json) {
console.log(JSON.stringify({ report, validation, organized }, null, 2));
} else {
console.log("");
console.log(formatDiscoveryValidation(report).trimEnd());
}

if (!validation.valid) {
process.exitCode = 1;
}
}
);

program
.command("format")
.description("Format capability files into canonical section order and refresh agent section comments")
Expand Down
Loading