codecall 1.0.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/ARCHITECTURE.md +553 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +109 -0
- package/dist/install-skill.d.ts +4 -0
- package/dist/install-skill.js +40 -0
- package/dist/policy/opportunity.d.ts +12 -0
- package/dist/policy/opportunity.js +126 -0
- package/dist/public.d.ts +12 -0
- package/dist/public.js +24 -0
- package/dist/runtime/event-store.d.ts +20 -0
- package/dist/runtime/event-store.js +32 -0
- package/dist/runtime/orchestrator.d.ts +28 -0
- package/dist/runtime/orchestrator.js +128 -0
- package/dist/runtime/reducer.d.ts +3 -0
- package/dist/runtime/reducer.js +43 -0
- package/dist/schemas/types.d.ts +136 -0
- package/dist/schemas/types.js +1 -0
- package/dist/schemas/validate.d.ts +3 -0
- package/dist/schemas/validate.js +17 -0
- package/dist/shared/id.d.ts +1 -0
- package/dist/shared/id.js +2 -0
- package/dist/workers/contracts.d.ts +25 -0
- package/dist/workers/contracts.js +1 -0
- package/dist/workers/default-workers.d.ts +29 -0
- package/dist/workers/default-workers.js +92 -0
- package/package.json +55 -0
- package/skill/SKILL.md +100 -0
- package/skill/agents/openai.yaml +4 -0
- package/skill/references/AGENTS.codecall.md +7 -0
- package/skill/references/CLAUDE.codecall.md +7 -0
- package/skill/references/trigger-policy.md +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# codecall
|
|
2
|
+
|
|
3
|
+
`codecall` is an agent-backed skill for Codex and Claude Code that turns a
|
|
4
|
+
completed implementation into a short, evidence-grounded, adaptive learning
|
|
5
|
+
session. It uses the active coding agent that already understands the task and
|
|
6
|
+
repository—there is no additional model call or external context upload.
|
|
7
|
+
|
|
8
|
+
The MVP deliberately teaches and checks understanding one step at a time:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
collect minimal context → recommend → confidence → lesson → one check-in
|
|
12
|
+
↑ ↓
|
|
13
|
+
└── reinforce / advance
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
It does not throw a fixed quiz at the user. A normal session covers 2–4 related
|
|
17
|
+
concepts, using two checks per concept: a real-world-to-technical mapping and a
|
|
18
|
+
technical application or tradeoff question. A wrong answer produces targeted
|
|
19
|
+
reinforcement and a fresh equivalent check-in; correct answers unlock the next
|
|
20
|
+
prerequisite-ready learning step.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
Install once from GitHub to add the same `codecall` skill to both your local Codex
|
|
25
|
+
and Claude Code skills directories:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g https://github.com/GAURAV-1313/codecall/archive/refs/heads/main.tar.gz
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Restart the relevant coding agent or open a new task so it reloads the installed
|
|
32
|
+
skill. This package does not require an additional API key, and its install step
|
|
33
|
+
does not upload your code.
|
|
34
|
+
|
|
35
|
+
When the package is published to npm, the equivalent registry command will be:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g codecall
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use it in Codex
|
|
42
|
+
|
|
43
|
+
After Codex completes an implementation, invoke:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
$codecall
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The skill uses the existing conversation and the coding agent's recent edits.
|
|
50
|
+
It can optionally inspect a Git diff when that is useful, but Git is never a
|
|
51
|
+
requirement.
|
|
52
|
+
It first presents a Start/Skip recommendation. After Start, it teaches one
|
|
53
|
+
implementation-specific concept and asks one reasoning question at a time.
|
|
54
|
+
Your answer determines whether it reinforces, deepens, or advances. It finishes
|
|
55
|
+
with concepts covered, weak areas, and an estimated session mastery. The final
|
|
56
|
+
summary also gives implementation-grounded points to remember and only the
|
|
57
|
+
relevant edge cases to check before applying the pattern in another project.
|
|
58
|
+
|
|
59
|
+
The installable skill source is in [`skill/`](skill/SKILL.md).
|
|
60
|
+
|
|
61
|
+
## Use it in Claude Code
|
|
62
|
+
|
|
63
|
+
After Claude Code completes an implementation, invoke:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/codecall
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Claude Code discovers personal skills from `~/.claude/skills/<skill-name>/SKILL.md`.
|
|
70
|
+
The installer creates `~/.claude/skills/codecall/SKILL.md`, using the same
|
|
71
|
+
Agent-Skills-standard instruction file as Codex. It uses Claude Code's current
|
|
72
|
+
conversation and edits—there is no extra learning-model API to configure.
|
|
73
|
+
|
|
74
|
+
For a repository-level automatic, non-blocking recommendation, add the same
|
|
75
|
+
rule below to `CLAUDE.md` rather than `AGENTS.md`.
|
|
76
|
+
|
|
77
|
+
### Automatic recommendation
|
|
78
|
+
|
|
79
|
+
Codex and Claude Code can select the skill implicitly, but skills are not
|
|
80
|
+
background event listeners. `$codecall` (Codex) and `/codecall` (Claude Code) are the
|
|
81
|
+
dependable triggers in any repository.
|
|
82
|
+
|
|
83
|
+
The policy is intentionally selective: it recommends only when an implementation
|
|
84
|
+
has one strong signal (for example a security boundary, integration, public
|
|
85
|
+
contract, or architecture decision) or two moderate signals (such as a reusable
|
|
86
|
+
pattern plus operational behavior). It skips cosmetic, documentation-only,
|
|
87
|
+
mechanical, generated-only, dependency-only, and ordinary test-only work. The
|
|
88
|
+
full policy is [installed with the skill](skill/references/trigger-policy.md).
|
|
89
|
+
|
|
90
|
+
To enable it in a project, copy the [Codex template](skill/references/AGENTS.codecall.md)
|
|
91
|
+
into `AGENTS.md` or the [Claude Code template](skill/references/CLAUDE.codecall.md)
|
|
92
|
+
into `CLAUDE.md`:
|
|
93
|
+
|
|
94
|
+
```md
|
|
95
|
+
Before the normal final response for a completed implementation, read the
|
|
96
|
+
codecall skill's references/trigger-policy.md and evaluate the change. Show Start
|
|
97
|
+
Learning / Skip only for a `recommend` outcome; never teach without Start.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The policy retains only an in-memory concept fingerprint for the current agent
|
|
101
|
+
session, preventing duplicate cards without tracking learning across sessions
|
|
102
|
+
or projects.
|
|
103
|
+
|
|
104
|
+
## Local demonstration runtime
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
codecall --from-git --task "Add OAuth protected routes"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The terminal command is a local, deterministic demonstration runtime. Use
|
|
111
|
+
`$codecall` in Codex for the primary agent-backed product experience. Use
|
|
112
|
+
`codecall --help` for command options.
|
|
113
|
+
|
|
114
|
+
## Use it programmatically
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { codecall } from "codecall";
|
|
118
|
+
|
|
119
|
+
const implementation = {
|
|
120
|
+
task: "Protect account routes using JWT authentication middleware.",
|
|
121
|
+
changedFiles: [{
|
|
122
|
+
path: "src/routes.ts",
|
|
123
|
+
summary: "Adds authentication middleware before account handlers."
|
|
124
|
+
}]
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const runtime = codecall(implementation);
|
|
128
|
+
const session = await runtime.start(implementation);
|
|
129
|
+
|
|
130
|
+
runtime.decide(session, true); // developer chose Start Learning
|
|
131
|
+
await runtime.setConfidence(session, "heard_of_it");
|
|
132
|
+
|
|
133
|
+
// Render session.currentQuestion. On a user answer:
|
|
134
|
+
await runtime.answer(session, "causal");
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Providers render `runtime.history(session.id)` as their native UI: `/codecall`, a
|
|
138
|
+
tool call, or a non-blocking post-task recommendation. Core workers never
|
|
139
|
+
depend on a provider SDK.
|
|
140
|
+
|
|
141
|
+
## What ships in v1.0.0
|
|
142
|
+
|
|
143
|
+
- append-only, typed session events and an explicit state machine;
|
|
144
|
+
- progressive minimal context represented as evidence references;
|
|
145
|
+
- explainable opportunity scoring based on concepts and architecture signals,
|
|
146
|
+
not LOC;
|
|
147
|
+
- separated concept categories, dependency-aware planning, and a bounded plan;
|
|
148
|
+
- an adaptive teach → one MCQ → evaluate → reinforce/advance loop;
|
|
149
|
+
- final transfer guidance: points to remember plus cross-project edge cases;
|
|
150
|
+
- one shared skill installed for Codex and Claude Code;
|
|
151
|
+
- an explainable strong/moderate auto-recommendation policy with session-local
|
|
152
|
+
duplicate suppression;
|
|
153
|
+
- pluggable worker and documentation-provider contracts;
|
|
154
|
+
- in-memory and caller-owned JSONL event stores;
|
|
155
|
+
- deterministic default workers suitable for local demonstration and tests.
|
|
156
|
+
|
|
157
|
+
The included deterministic workers support local tests and the terminal demo.
|
|
158
|
+
The installed agent skill is the production path: its reasoning, concept
|
|
159
|
+
selection, teaching, question generation, and evaluation are performed by the
|
|
160
|
+
active coding agent in the existing conversation.
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm install
|
|
166
|
+
npm run build
|
|
167
|
+
npm test
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full design, privacy posture,
|
|
171
|
+
event model, state machine, and planned provider integrations.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
6
|
+
import { codecall } from "./public.js";
|
|
7
|
+
function parseArguments(values) {
|
|
8
|
+
const parsed = { fromGit: false, help: false };
|
|
9
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
10
|
+
if (values[index] === "--task")
|
|
11
|
+
parsed.task = values[++index];
|
|
12
|
+
else if (values[index] === "--from-git")
|
|
13
|
+
parsed.fromGit = true;
|
|
14
|
+
else if (values[index] === "--help" || values[index] === "-h")
|
|
15
|
+
parsed.help = true;
|
|
16
|
+
else
|
|
17
|
+
throw new Error(`Unknown argument: ${values[index]}`);
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
function help() {
|
|
22
|
+
console.log(`Usage: codecall --task "Describe the implementation" [--from-git]
|
|
23
|
+
|
|
24
|
+
Starts an adaptive learning session. --from-git adds changed-file summaries and
|
|
25
|
+
small excerpts from the current working tree. For the full agent-backed
|
|
26
|
+
experience, invoke $codecall in Codex after completing an implementation.`);
|
|
27
|
+
}
|
|
28
|
+
function changedFilesFromGit() {
|
|
29
|
+
const options = { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] };
|
|
30
|
+
try {
|
|
31
|
+
// Porcelain status includes staged, unstaged, and newly-created files, even
|
|
32
|
+
// in a repository that has not created its first commit yet.
|
|
33
|
+
const names = execFileSync("git", ["status", "--porcelain"], options)
|
|
34
|
+
.split("\n")
|
|
35
|
+
.map((line) => line.slice(3).trim())
|
|
36
|
+
.map((path) => path.includes(" -> ") ? path.split(" -> ").at(-1) : path)
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
return names.slice(0, 12).map((path) => {
|
|
39
|
+
let excerpt;
|
|
40
|
+
try {
|
|
41
|
+
excerpt = readFileSync(path, "utf8").slice(0, 1_500);
|
|
42
|
+
}
|
|
43
|
+
catch { /* deleted or binary file */ }
|
|
44
|
+
return { path, summary: "Changed in the current Git diff.", excerpt };
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function latest(events, type) {
|
|
52
|
+
return [...events].reverse().find((event) => event.type === type)?.payload;
|
|
53
|
+
}
|
|
54
|
+
function printLesson(lesson) {
|
|
55
|
+
console.log(`\n${lesson.title}\n${lesson.what}\n\nWhy here: ${lesson.whyHere}\nWhat breaks: ${lesson.failureMode}\nMental model: ${lesson.mentalModel}`);
|
|
56
|
+
}
|
|
57
|
+
async function main() {
|
|
58
|
+
const args = parseArguments(process.argv.slice(2));
|
|
59
|
+
if (args.help)
|
|
60
|
+
return help();
|
|
61
|
+
const rl = createInterface({ input, output });
|
|
62
|
+
try {
|
|
63
|
+
const task = args.task ?? await rl.question("What implementation did you complete? ");
|
|
64
|
+
const implementation = { task, changedFiles: args.fromGit ? changedFilesFromGit() : [] };
|
|
65
|
+
const runtime = codecall(implementation);
|
|
66
|
+
const session = await runtime.start(implementation);
|
|
67
|
+
const opportunity = session.opportunity;
|
|
68
|
+
console.log(`\nLearning opportunity: ${opportunity.recommendation} · ${opportunity.estimatedMinutes} minutes\n${opportunity.reasoning.join(" ")}`);
|
|
69
|
+
const accepted = /^(y|yes)$/i.test(await rl.question("Start learning? [y/N] "));
|
|
70
|
+
runtime.decide(session, accepted);
|
|
71
|
+
if (!accepted) {
|
|
72
|
+
console.log("Learning skipped.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const confidence = (await rl.question("Confidence [expert / comfortable / heard_of_it / never_learned]: ")).trim();
|
|
76
|
+
if (!["expert", "comfortable", "heard_of_it", "never_learned"].includes(confidence)) {
|
|
77
|
+
throw new Error("Choose expert, comfortable, heard_of_it, or never_learned.");
|
|
78
|
+
}
|
|
79
|
+
await runtime.setConfidence(session, confidence);
|
|
80
|
+
let renderedLessonCount = 0;
|
|
81
|
+
while (session.state === "quiz" && session.currentQuestion) {
|
|
82
|
+
const events = runtime.history(session.id);
|
|
83
|
+
const lessons = events.filter((event) => event.type === "LESSON_READY");
|
|
84
|
+
if (lessons.length > renderedLessonCount) {
|
|
85
|
+
printLesson(latest(events, "LESSON_READY"));
|
|
86
|
+
renderedLessonCount = lessons.length;
|
|
87
|
+
}
|
|
88
|
+
const question = session.currentQuestion;
|
|
89
|
+
console.log(`\n${question.prompt}`);
|
|
90
|
+
question.choices.forEach((choice, index) => console.log(` ${index + 1}. ${choice.text}`));
|
|
91
|
+
const selected = Number(await rl.question("Your answer: ")) - 1;
|
|
92
|
+
const choice = question.choices[selected];
|
|
93
|
+
if (!choice) {
|
|
94
|
+
console.log("Choose one of the displayed numbers.");
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
await runtime.answer(session, choice.id);
|
|
98
|
+
const feedback = latest(runtime.history(session.id), "ANSWER_EVALUATED");
|
|
99
|
+
if (feedback)
|
|
100
|
+
console.log(`\n${feedback.feedback}`);
|
|
101
|
+
}
|
|
102
|
+
if (session.summary)
|
|
103
|
+
console.log(`\nSession complete\nMastery: ${session.summary.estimatedSessionMastery}\nTakeaways:\n- ${session.summary.keyTakeaways.join("\n- ")}\nPoints to remember:\n- ${session.summary.pointsToRemember.join("\n- ")}\nEdge cases for other projects:\n- ${session.summary.edgeCasesForOtherProjects.join("\n- ")}`);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
rl.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
main().catch((error) => { console.error(error instanceof Error ? error.message : error); process.exitCode = 1; });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Copy the packaged skill into Codex's discoverable skill directory. */
|
|
2
|
+
export declare function installSkill(destinationRoot?: string): string;
|
|
3
|
+
/** Copy the same Agent Skills-standard skill into Claude Code's personal directory. */
|
|
4
|
+
export declare function installClaudeSkill(destinationRoot?: string): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
function packagedSkillSource() {
|
|
6
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
return join(here, "..", "skill");
|
|
8
|
+
}
|
|
9
|
+
function copySkill(destinationRoot) {
|
|
10
|
+
const source = packagedSkillSource();
|
|
11
|
+
if (!existsSync(source))
|
|
12
|
+
throw new Error(`Packaged skill source was not found: ${source}`);
|
|
13
|
+
mkdirSync(destinationRoot, { recursive: true });
|
|
14
|
+
const destination = join(destinationRoot, "codecall");
|
|
15
|
+
cpSync(source, destination, { recursive: true, force: true });
|
|
16
|
+
const legacySkill = join(destinationRoot, "learn");
|
|
17
|
+
if (existsSync(legacySkill))
|
|
18
|
+
rmSync(legacySkill, { recursive: true, force: true });
|
|
19
|
+
return destination;
|
|
20
|
+
}
|
|
21
|
+
/** Copy the packaged skill into Codex's discoverable skill directory. */
|
|
22
|
+
export function installSkill(destinationRoot = process.env.CODEX_HOME ? join(process.env.CODEX_HOME, "skills") : join(homedir(), ".codex", "skills")) {
|
|
23
|
+
return copySkill(destinationRoot);
|
|
24
|
+
}
|
|
25
|
+
/** Copy the same Agent Skills-standard skill into Claude Code's personal directory. */
|
|
26
|
+
export function installClaudeSkill(destinationRoot = process.env.CLAUDE_CONFIG_DIR ? join(process.env.CLAUDE_CONFIG_DIR, "skills") : join(homedir(), ".claude", "skills")) {
|
|
27
|
+
return copySkill(destinationRoot);
|
|
28
|
+
}
|
|
29
|
+
const invokedAsScript = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
30
|
+
if (invokedAsScript) {
|
|
31
|
+
if (process.argv.includes("--global-only") && process.env.npm_config_global !== "true")
|
|
32
|
+
process.exit(0);
|
|
33
|
+
try {
|
|
34
|
+
console.log(`Installed Codex skill at ${installSkill()}`);
|
|
35
|
+
console.log(`Installed Claude Code skill at ${installClaudeSkill()}`);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.warn(`Could not install the codecall skill: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ImplementationContext, LearningOpportunity } from "../schemas/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic parity policy for the agent-backed skill. It is deliberately
|
|
4
|
+
* evidence-based and selective; it is not the production authority on a
|
|
5
|
+
* conversation's technical meaning.
|
|
6
|
+
*/
|
|
7
|
+
export declare function evaluateLearningOpportunity(context: ImplementationContext): LearningOpportunity;
|
|
8
|
+
/** Deduplicates concept clusters for the lifetime of one runtime/session only. */
|
|
9
|
+
export declare class SessionOpportunityDeduper {
|
|
10
|
+
private readonly evaluatedFingerprints;
|
|
11
|
+
evaluate(context: ImplementationContext): LearningOpportunity;
|
|
12
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const strongRules = [
|
|
2
|
+
{ id: "external_contract_or_integration", match: /\b(oauth|webhook|sdk|integration|third[- ]party|api endpoint|public api|protocol|schema|migration|openapi|graphql)\b/i },
|
|
3
|
+
{ id: "architecture_or_cross_component_flow", match: /\b(middleware|pipeline|cross[- ]?(service|component)|event[- ]?driven|controller|service layer|handler|boundary)\b/i },
|
|
4
|
+
{ id: "security_or_reliability_boundary", match: /\b(auth(?:entication|orization)?|jwt|token|permission|security|encrypt|concurrenc\w*|race condition|retry|backoff|timeout|idempoten\w*|transaction|consisten\w*|failure handling|error handling)\b/i },
|
|
5
|
+
{ id: "consequential_tradeoff", match: /\b(trade[- ]?off|design decision|chose|choose|instead of|compatib(?:ility|le)|backward[- ]?compatible)\b/i }
|
|
6
|
+
];
|
|
7
|
+
const moderateRules = [
|
|
8
|
+
{ id: "reusable_pattern_or_lifecycle", match: /\b(adapter|strategy|factory|observer|cache|queue|algorithm|state machine|lifecycle|framework)\b/i },
|
|
9
|
+
{ id: "operational_behavior", match: /\b(config(?:uration)?|environment variable|feature flag|log(?:ging)?|metric|trace|observability|validat(?:e|ion)|test harness|fixture|mock|e2e|ci|deploy(?:ment)?)\b/i }
|
|
10
|
+
];
|
|
11
|
+
const excludedRules = [
|
|
12
|
+
{ id: "formatting_or_copy", match: /\b(format(?:ting)?|prettier|lint(?:ing)?|whitespace|copy change|comment(?:s)?|typo)\b/i },
|
|
13
|
+
{ id: "documentation_only", match: /\b(readme|documentation|docs?|changelog)\b/i },
|
|
14
|
+
{ id: "mechanical_rename", match: /\b(rename|renaming|move(?:d)? files?)\b/i },
|
|
15
|
+
{ id: "dependency_bump", match: /\b(dependency|package) (?:bump|update|upgrade)\b/i },
|
|
16
|
+
{ id: "straightforward_local_fix", match: /\b(simple|straightforward|localized|local) (?:bug )?fix\b/i }
|
|
17
|
+
];
|
|
18
|
+
function implementationText(context) {
|
|
19
|
+
const { implementation } = context;
|
|
20
|
+
return `${implementation.task} ${implementation.conversationSummary ?? ""} ${implementation.changedFiles.map((file) => `${file.path} ${file.summary} ${file.excerpt ?? ""}`).join(" ")}`;
|
|
21
|
+
}
|
|
22
|
+
function isGenerated(file) {
|
|
23
|
+
return /(^|\/)(dist|build|coverage|generated)(\/|$)|\.(lock|snap)$/i.test(file.path) || /generated file/i.test(file.summary);
|
|
24
|
+
}
|
|
25
|
+
function isTestFile(file) {
|
|
26
|
+
return /(^|\/)(test|tests|__tests__|spec)(\/|$)|\.(test|spec)\.[cm]?[jt]sx?$/i.test(file.path);
|
|
27
|
+
}
|
|
28
|
+
function isDocumentationFile(file) {
|
|
29
|
+
return /(^|\/)(readme|changelog|docs?)(\/|\.|$)|\.(md|mdx|txt)$/i.test(file.path);
|
|
30
|
+
}
|
|
31
|
+
function isDependencyFile(file) {
|
|
32
|
+
return /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|composer\.lock|poetry\.lock)$/i.test(file.path);
|
|
33
|
+
}
|
|
34
|
+
function uniqueMatches(text, rules) {
|
|
35
|
+
return rules.filter((rule) => rule.match.test(text)).map((rule) => rule.id);
|
|
36
|
+
}
|
|
37
|
+
function sourceLabel(context) {
|
|
38
|
+
const file = context.implementation.changedFiles.find((candidate) => !isGenerated(candidate));
|
|
39
|
+
if (file)
|
|
40
|
+
return file.path;
|
|
41
|
+
const evidence = context.evidence.find((candidate) => candidate.path || candidate.label);
|
|
42
|
+
return evidence?.path ?? evidence?.label ?? "the completed implementation";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Deterministic parity policy for the agent-backed skill. It is deliberately
|
|
46
|
+
* evidence-based and selective; it is not the production authority on a
|
|
47
|
+
* conversation's technical meaning.
|
|
48
|
+
*/
|
|
49
|
+
export function evaluateLearningOpportunity(context) {
|
|
50
|
+
const { implementation } = context;
|
|
51
|
+
const text = implementationText(context);
|
|
52
|
+
const excludedChangeCategories = uniqueMatches(text, excludedRules);
|
|
53
|
+
if (implementation.changedFiles.some(isGenerated))
|
|
54
|
+
excludedChangeCategories.push("generated_or_lockfile_change");
|
|
55
|
+
const inspectable = Boolean(implementation.task.trim()) && (context.evidence.length > 0 || implementation.changedFiles.length > 0);
|
|
56
|
+
const strongSignals = uniqueMatches(text, strongRules);
|
|
57
|
+
const moderateSignals = uniqueMatches(text, moderateRules);
|
|
58
|
+
const changedSourceFiles = implementation.changedFiles.filter((file) => !isGenerated(file) && !isTestFile(file) && !isDocumentationFile(file) && !isDependencyFile(file));
|
|
59
|
+
const testFiles = implementation.changedFiles.filter(isTestFile);
|
|
60
|
+
const testStrategy = moderateSignals.includes("operational_behavior");
|
|
61
|
+
if (changedSourceFiles.length >= 2 && /\b(import|depend(?:s|ency|ent)|consumer|producer|route|service|handler)\b/i.test(text)) {
|
|
62
|
+
moderateSignals.push("cross_component_dependency");
|
|
63
|
+
}
|
|
64
|
+
if (testFiles.length > 0 && changedSourceFiles.length === 0 && !testStrategy)
|
|
65
|
+
excludedChangeCategories.push("test_only_without_strategy");
|
|
66
|
+
const uniqueStrong = [...new Set(strongSignals)];
|
|
67
|
+
const uniqueModerate = [...new Set(moderateSignals)];
|
|
68
|
+
const meaningfulEvidence = changedSourceFiles.length > 0 || (testFiles.length > 0 && testStrategy);
|
|
69
|
+
const onlyExcluded = implementation.changedFiles.length > 0 && !meaningfulEvidence;
|
|
70
|
+
const conceptFingerprint = `learning-policy/v1:${[...uniqueStrong, ...uniqueModerate].sort().join("+") || "no-meaningful-cluster"}`;
|
|
71
|
+
if (!inspectable || onlyExcluded) {
|
|
72
|
+
const reason = !inspectable
|
|
73
|
+
? "Skipped automatic learning because the completed implementation lacks inspectable task or file evidence."
|
|
74
|
+
: `Skipped automatic learning because the change is limited to ${[...new Set(excludedChangeCategories)].join(", ")}.`;
|
|
75
|
+
return {
|
|
76
|
+
score: 0,
|
|
77
|
+
confidence: 0.9,
|
|
78
|
+
estimatedMinutes: 0,
|
|
79
|
+
recommendation: "skip",
|
|
80
|
+
signals: {},
|
|
81
|
+
strongSignals: uniqueStrong,
|
|
82
|
+
moderateSignals: uniqueModerate,
|
|
83
|
+
excludedChangeCategories: [...new Set(excludedChangeCategories)],
|
|
84
|
+
conceptFingerprint,
|
|
85
|
+
reasoning: [reason, "Manual /codecall or $codecall remains available."]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const recommendation = uniqueStrong.length >= 1 || uniqueModerate.length >= 2
|
|
89
|
+
? "recommend"
|
|
90
|
+
: uniqueModerate.length === 1 ? "optional" : "skip";
|
|
91
|
+
const score = Number(Math.min(1, uniqueStrong.length * 0.65 + uniqueModerate.length * 0.3).toFixed(2));
|
|
92
|
+
const signalNames = [...uniqueStrong, ...uniqueModerate];
|
|
93
|
+
const reason = signalNames.length
|
|
94
|
+
? `Implementation evidence in ${sourceLabel(context)} indicates ${signalNames.join(", ")}.`
|
|
95
|
+
: `No strong signal or pair of moderate signals was found in ${sourceLabel(context)}.`;
|
|
96
|
+
return {
|
|
97
|
+
score,
|
|
98
|
+
confidence: 0.8,
|
|
99
|
+
estimatedMinutes: recommendation === "recommend" ? Math.min(8, 3 + signalNames.length) : recommendation === "optional" ? 3 : 0,
|
|
100
|
+
recommendation,
|
|
101
|
+
signals: Object.fromEntries([...uniqueStrong.map((id) => [id, 1]), ...uniqueModerate.map((id) => [id, 0.5])]),
|
|
102
|
+
strongSignals: uniqueStrong,
|
|
103
|
+
moderateSignals: uniqueModerate,
|
|
104
|
+
excludedChangeCategories: [...new Set(excludedChangeCategories)],
|
|
105
|
+
conceptFingerprint,
|
|
106
|
+
reasoning: [reason, "Decision is based on implementation evidence and policy signals, never line count."]
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Deduplicates concept clusters for the lifetime of one runtime/session only. */
|
|
110
|
+
export class SessionOpportunityDeduper {
|
|
111
|
+
evaluatedFingerprints = new Set();
|
|
112
|
+
evaluate(context) {
|
|
113
|
+
const opportunity = evaluateLearningOpportunity(context);
|
|
114
|
+
if (this.evaluatedFingerprints.has(opportunity.conceptFingerprint)) {
|
|
115
|
+
return {
|
|
116
|
+
...opportunity,
|
|
117
|
+
recommendation: "skip",
|
|
118
|
+
estimatedMinutes: 0,
|
|
119
|
+
excludedChangeCategories: [...opportunity.excludedChangeCategories, "already_evaluated_in_session"],
|
|
120
|
+
reasoning: [`Skipped automatic learning because ${opportunity.conceptFingerprint} was already evaluated in this session.`, "Manual /codecall or $codecall remains available."]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
this.evaluatedFingerprints.add(opportunity.conceptFingerprint);
|
|
124
|
+
return opportunity;
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/public.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ImplementationLocator } from "./schemas/types.js";
|
|
2
|
+
import { LearnRuntime } from "./runtime/orchestrator.js";
|
|
3
|
+
/** Creates the provider-neutral MVP capability. Providers render its emitted events however they choose. */
|
|
4
|
+
/**
|
|
5
|
+
* Local demonstration runtime. The production experience is the agent-backed
|
|
6
|
+
* Codex skill, which uses the active coding agent's reasoning directly.
|
|
7
|
+
*/
|
|
8
|
+
export declare function codecall(implementation: ImplementationLocator): LearnRuntime;
|
|
9
|
+
export * from "./schemas/types.js";
|
|
10
|
+
export * from "./policy/opportunity.js";
|
|
11
|
+
export * from "./runtime/orchestrator.js";
|
|
12
|
+
export * from "./runtime/event-store.js";
|
package/dist/public.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MemoryEventStore } from "./runtime/event-store.js";
|
|
2
|
+
import { LearnRuntime, contextFromImplementation } from "./runtime/orchestrator.js";
|
|
3
|
+
import { DefaultContextCollector, DependencyLearningPlanner, DeterministicEvaluationEngine, GroundedTeachingEngine, HeuristicConceptExtractor, HeuristicOpportunityDetector, NoopDocumentationResolver, ReasoningQuizEngine } from "./workers/default-workers.js";
|
|
4
|
+
/** Creates the provider-neutral MVP capability. Providers render its emitted events however they choose. */
|
|
5
|
+
/**
|
|
6
|
+
* Local demonstration runtime. The production experience is the agent-backed
|
|
7
|
+
* Codex skill, which uses the active coding agent's reasoning directly.
|
|
8
|
+
*/
|
|
9
|
+
export function codecall(implementation) {
|
|
10
|
+
return new LearnRuntime({
|
|
11
|
+
contextCollector: new DefaultContextCollector(contextFromImplementation(implementation)),
|
|
12
|
+
opportunityDetector: new HeuristicOpportunityDetector(),
|
|
13
|
+
conceptExtractor: new HeuristicConceptExtractor(),
|
|
14
|
+
learningPlanner: new DependencyLearningPlanner(),
|
|
15
|
+
documentationResolver: new NoopDocumentationResolver(),
|
|
16
|
+
teachingEngine: new GroundedTeachingEngine(),
|
|
17
|
+
quizEngine: new ReasoningQuizEngine(),
|
|
18
|
+
evaluationEngine: new DeterministicEvaluationEngine()
|
|
19
|
+
}, new MemoryEventStore());
|
|
20
|
+
}
|
|
21
|
+
export * from "./schemas/types.js";
|
|
22
|
+
export * from "./policy/opportunity.js";
|
|
23
|
+
export * from "./runtime/orchestrator.js";
|
|
24
|
+
export * from "./runtime/event-store.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LearnEvent } from "../schemas/types.js";
|
|
2
|
+
export interface EventStore {
|
|
3
|
+
append(event: LearnEvent): void;
|
|
4
|
+
read(sessionId: string): LearnEvent[];
|
|
5
|
+
}
|
|
6
|
+
export declare class MemoryEventStore implements EventStore {
|
|
7
|
+
private readonly events;
|
|
8
|
+
append(event: LearnEvent): void;
|
|
9
|
+
read(sessionId: string): LearnEvent[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A deliberately small caller-owned persistence option. The runtime never
|
|
13
|
+
* chooses a storage location or sends events elsewhere; the adapter supplies it.
|
|
14
|
+
*/
|
|
15
|
+
export declare class JsonlEventStore implements EventStore {
|
|
16
|
+
private readonly filePath;
|
|
17
|
+
constructor(filePath: string);
|
|
18
|
+
append(event: LearnEvent): void;
|
|
19
|
+
read(sessionId: string): LearnEvent[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
2
|
+
export class MemoryEventStore {
|
|
3
|
+
events = [];
|
|
4
|
+
append(event) {
|
|
5
|
+
this.events.push(structuredClone(event));
|
|
6
|
+
}
|
|
7
|
+
read(sessionId) {
|
|
8
|
+
return this.events.filter((event) => event.sessionId === sessionId).map((event) => structuredClone(event));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A deliberately small caller-owned persistence option. The runtime never
|
|
13
|
+
* chooses a storage location or sends events elsewhere; the adapter supplies it.
|
|
14
|
+
*/
|
|
15
|
+
export class JsonlEventStore {
|
|
16
|
+
filePath;
|
|
17
|
+
constructor(filePath) {
|
|
18
|
+
this.filePath = filePath;
|
|
19
|
+
}
|
|
20
|
+
append(event) {
|
|
21
|
+
appendFileSync(this.filePath, `${JSON.stringify(event)}\n`, "utf8");
|
|
22
|
+
}
|
|
23
|
+
read(sessionId) {
|
|
24
|
+
if (!existsSync(this.filePath))
|
|
25
|
+
return [];
|
|
26
|
+
return readFileSync(this.filePath, "utf8")
|
|
27
|
+
.split("\n")
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.map((line) => JSON.parse(line))
|
|
30
|
+
.filter((event) => event.sessionId === sessionId);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ConfidenceLevel, ImplementationContext, ImplementationLocator, LearnEvent, Session } from "../schemas/types.js";
|
|
2
|
+
import type { EventStore } from "./event-store.js";
|
|
3
|
+
import type { ConceptExtractor, ContextCollector, DocumentationResolver, EvaluationEngine, LearningPlanner, OpportunityDetector, QuizEngine, TeachingEngine } from "../workers/contracts.js";
|
|
4
|
+
export interface RuntimeWorkers {
|
|
5
|
+
contextCollector: ContextCollector;
|
|
6
|
+
opportunityDetector: OpportunityDetector;
|
|
7
|
+
conceptExtractor: ConceptExtractor;
|
|
8
|
+
learningPlanner: LearningPlanner;
|
|
9
|
+
documentationResolver: DocumentationResolver;
|
|
10
|
+
teachingEngine: TeachingEngine;
|
|
11
|
+
quizEngine: QuizEngine;
|
|
12
|
+
evaluationEngine: EvaluationEngine;
|
|
13
|
+
}
|
|
14
|
+
export declare class LearnRuntime {
|
|
15
|
+
private readonly workers;
|
|
16
|
+
private readonly events;
|
|
17
|
+
constructor(workers: RuntimeWorkers, events: EventStore);
|
|
18
|
+
start(implementation: ImplementationLocator): Promise<Session>;
|
|
19
|
+
decide(session: Session, accepted: boolean): Session;
|
|
20
|
+
setConfidence(session: Session, confidence: ConfidenceLevel): Promise<Session>;
|
|
21
|
+
answer(session: Session, choiceId: string): Promise<Session>;
|
|
22
|
+
cancel(session: Session, reason?: string): Session;
|
|
23
|
+
history(sessionId: string): LearnEvent[];
|
|
24
|
+
private presentCurrentLearningStep;
|
|
25
|
+
private summarize;
|
|
26
|
+
private emit;
|
|
27
|
+
}
|
|
28
|
+
export declare function contextFromImplementation(implementation: ImplementationLocator): ImplementationContext;
|