opencode-goopspec 0.1.0 → 0.1.2
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/LICENSE +1 -1
- package/README.md +17 -35
- package/agents/goop-debugger.md +199 -11
- package/agents/goop-designer.md +194 -1
- package/agents/goop-executor.md +169 -19
- package/agents/goop-explorer.md +162 -1
- package/agents/goop-librarian.md +156 -1
- package/agents/goop-orchestrator.md +271 -1
- package/agents/goop-planner.md +147 -13
- package/agents/goop-researcher.md +151 -1
- package/agents/goop-tester.md +188 -1
- package/agents/goop-verifier.md +181 -1
- package/agents/goop-writer.md +175 -1
- package/agents/memory-distiller.md +20 -0
- package/commands/goop-help.md +1 -1
- package/dist/index.js +852 -23
- package/package.json +4 -4
- package/references/dispatch-patterns.md +35 -0
- package/references/response-format.md +386 -0
- package/skills/task-delegation/skill.md +50 -3
package/dist/index.js
CHANGED
|
@@ -27635,6 +27635,175 @@ Available agents: ${available.join(", ")}`;
|
|
|
27635
27635
|
});
|
|
27636
27636
|
}
|
|
27637
27637
|
|
|
27638
|
+
// src/tools/goop-reference/index.ts
|
|
27639
|
+
function formatResourceInfo(resource, _category) {
|
|
27640
|
+
const desc = resource.frontmatter.description ? `: ${resource.frontmatter.description}` : "";
|
|
27641
|
+
const categoryTag = resource.frontmatter.category ? ` (${resource.frontmatter.category})` : "";
|
|
27642
|
+
return `- **${resource.name}**${desc}${categoryTag}`;
|
|
27643
|
+
}
|
|
27644
|
+
function getSection(resource, section) {
|
|
27645
|
+
if (!section)
|
|
27646
|
+
return null;
|
|
27647
|
+
const sectionLower = section.toLowerCase();
|
|
27648
|
+
for (const [key, value] of Object.entries(resource.frontmatter)) {
|
|
27649
|
+
if (key.toLowerCase() === sectionLower && typeof value === "string") {
|
|
27650
|
+
return value;
|
|
27651
|
+
}
|
|
27652
|
+
}
|
|
27653
|
+
const lines = resource.body.split(`
|
|
27654
|
+
`);
|
|
27655
|
+
let capturing = false;
|
|
27656
|
+
let capturedLines = [];
|
|
27657
|
+
const sectionPattern = new RegExp(`^##?\\s*${section}`, "i");
|
|
27658
|
+
for (const line of lines) {
|
|
27659
|
+
if (sectionPattern.test(line)) {
|
|
27660
|
+
capturing = true;
|
|
27661
|
+
capturedLines = [line];
|
|
27662
|
+
continue;
|
|
27663
|
+
}
|
|
27664
|
+
if (capturing) {
|
|
27665
|
+
if (/^##?\s/.test(line) && !line.startsWith("###")) {
|
|
27666
|
+
break;
|
|
27667
|
+
}
|
|
27668
|
+
capturedLines.push(line);
|
|
27669
|
+
}
|
|
27670
|
+
}
|
|
27671
|
+
return capturedLines.length > 0 ? capturedLines.join(`
|
|
27672
|
+
`).trim() : null;
|
|
27673
|
+
}
|
|
27674
|
+
function createGoopReferenceTool(ctx) {
|
|
27675
|
+
return tool({
|
|
27676
|
+
description: `Load reference documents or templates for specialized knowledge.
|
|
27677
|
+
|
|
27678
|
+
**References** provide protocols, checklists, and patterns:
|
|
27679
|
+
- subagent-protocol: How subagents communicate with orchestrator
|
|
27680
|
+
- deviation-rules: When to auto-fix vs ask user
|
|
27681
|
+
- security-checklist: Security verification checklist
|
|
27682
|
+
- response-format: Standardized agent response formats
|
|
27683
|
+
|
|
27684
|
+
**Templates** provide document structures:
|
|
27685
|
+
- spec: SPEC.md template for requirements
|
|
27686
|
+
- blueprint: BLUEPRINT.md template for execution plans
|
|
27687
|
+
- chronicle: CHRONICLE.md template for progress tracking
|
|
27688
|
+
|
|
27689
|
+
Use \`list: true\` to see all available resources.
|
|
27690
|
+
Use \`type\` to filter: "reference" or "template" or "all".
|
|
27691
|
+
Use \`section\` to extract a specific section from the document.`,
|
|
27692
|
+
args: {
|
|
27693
|
+
name: tool.schema.string().optional(),
|
|
27694
|
+
type: tool.schema.enum(["reference", "template", "all"]).optional(),
|
|
27695
|
+
section: tool.schema.string().optional(),
|
|
27696
|
+
list: tool.schema.boolean().optional()
|
|
27697
|
+
},
|
|
27698
|
+
async execute(args, _context) {
|
|
27699
|
+
const resourceType = args.type || "all";
|
|
27700
|
+
if (args.list || !args.name) {
|
|
27701
|
+
const lines2 = [];
|
|
27702
|
+
if (resourceType === "all" || resourceType === "reference") {
|
|
27703
|
+
const refs = ctx.resolver.resolveAll("reference");
|
|
27704
|
+
if (refs.length > 0) {
|
|
27705
|
+
lines2.push(`# Available References
|
|
27706
|
+
`);
|
|
27707
|
+
lines2.push(`*Protocols, checklists, and patterns for agents*
|
|
27708
|
+
`);
|
|
27709
|
+
for (const ref of refs) {
|
|
27710
|
+
lines2.push(formatResourceInfo(ref, "reference"));
|
|
27711
|
+
}
|
|
27712
|
+
lines2.push("");
|
|
27713
|
+
}
|
|
27714
|
+
}
|
|
27715
|
+
if (resourceType === "all" || resourceType === "template") {
|
|
27716
|
+
const templates = ctx.resolver.resolveAll("template");
|
|
27717
|
+
if (templates.length > 0) {
|
|
27718
|
+
lines2.push(`# Available Templates
|
|
27719
|
+
`);
|
|
27720
|
+
lines2.push(`*Document structures for GoopSpec artifacts*
|
|
27721
|
+
`);
|
|
27722
|
+
for (const tmpl of templates) {
|
|
27723
|
+
lines2.push(formatResourceInfo(tmpl, "template"));
|
|
27724
|
+
}
|
|
27725
|
+
lines2.push("");
|
|
27726
|
+
}
|
|
27727
|
+
}
|
|
27728
|
+
if (lines2.length === 0) {
|
|
27729
|
+
return "No references or templates found.";
|
|
27730
|
+
}
|
|
27731
|
+
lines2.push("---");
|
|
27732
|
+
lines2.push('Use `goop_reference({ name: "resource-name" })` to load content.');
|
|
27733
|
+
lines2.push('Use `goop_reference({ name: "resource-name", section: "Section Name" })` to extract a specific section.');
|
|
27734
|
+
return lines2.join(`
|
|
27735
|
+
`);
|
|
27736
|
+
}
|
|
27737
|
+
const name = args.name;
|
|
27738
|
+
let resource = null;
|
|
27739
|
+
let foundType = "reference";
|
|
27740
|
+
if (resourceType === "all" || resourceType === "reference") {
|
|
27741
|
+
resource = ctx.resolver.resolve("reference", name);
|
|
27742
|
+
if (resource)
|
|
27743
|
+
foundType = "reference";
|
|
27744
|
+
}
|
|
27745
|
+
if (!resource && (resourceType === "all" || resourceType === "template")) {
|
|
27746
|
+
resource = ctx.resolver.resolve("template", name);
|
|
27747
|
+
if (resource)
|
|
27748
|
+
foundType = "template";
|
|
27749
|
+
}
|
|
27750
|
+
if (!resource) {
|
|
27751
|
+
const availableRefs = ctx.resolver.resolveAll("reference").map((r) => r.name);
|
|
27752
|
+
const availableTemplates = ctx.resolver.resolveAll("template").map((t) => t.name);
|
|
27753
|
+
return [
|
|
27754
|
+
`Resource "${name}" not found.`,
|
|
27755
|
+
"",
|
|
27756
|
+
"**Available references:**",
|
|
27757
|
+
availableRefs.join(", ") || "(none)",
|
|
27758
|
+
"",
|
|
27759
|
+
"**Available templates:**",
|
|
27760
|
+
availableTemplates.join(", ") || "(none)"
|
|
27761
|
+
].join(`
|
|
27762
|
+
`);
|
|
27763
|
+
}
|
|
27764
|
+
if (args.section) {
|
|
27765
|
+
const sectionContent = getSection(resource, args.section);
|
|
27766
|
+
if (sectionContent) {
|
|
27767
|
+
return [
|
|
27768
|
+
`# ${resource.name} - ${args.section}`,
|
|
27769
|
+
"",
|
|
27770
|
+
`*Extracted from ${foundType}: ${resource.name}*`,
|
|
27771
|
+
"",
|
|
27772
|
+
"---",
|
|
27773
|
+
"",
|
|
27774
|
+
sectionContent
|
|
27775
|
+
].join(`
|
|
27776
|
+
`);
|
|
27777
|
+
} else {
|
|
27778
|
+
return [
|
|
27779
|
+
`Section "${args.section}" not found in ${resource.name}.`,
|
|
27780
|
+
"",
|
|
27781
|
+
"**Available content:**",
|
|
27782
|
+
resource.body.slice(0, 500) + (resource.body.length > 500 ? "..." : "")
|
|
27783
|
+
].join(`
|
|
27784
|
+
`);
|
|
27785
|
+
}
|
|
27786
|
+
}
|
|
27787
|
+
const lines = [
|
|
27788
|
+
`# ${foundType === "reference" ? "Reference" : "Template"}: ${resource.frontmatter.name || resource.name}`,
|
|
27789
|
+
""
|
|
27790
|
+
];
|
|
27791
|
+
if (resource.frontmatter.description) {
|
|
27792
|
+
lines.push(`**Description:** ${resource.frontmatter.description}`, "");
|
|
27793
|
+
}
|
|
27794
|
+
if (resource.frontmatter.category) {
|
|
27795
|
+
lines.push(`**Category:** ${resource.frontmatter.category}`, "");
|
|
27796
|
+
}
|
|
27797
|
+
if (resource.frontmatter.version) {
|
|
27798
|
+
lines.push(`**Version:** ${resource.frontmatter.version}`, "");
|
|
27799
|
+
}
|
|
27800
|
+
lines.push("---", "", resource.body);
|
|
27801
|
+
return lines.join(`
|
|
27802
|
+
`);
|
|
27803
|
+
}
|
|
27804
|
+
});
|
|
27805
|
+
}
|
|
27806
|
+
|
|
27638
27807
|
// src/tools/slashcommand/index.ts
|
|
27639
27808
|
function buildDescription(ctx) {
|
|
27640
27809
|
const commands = ctx.resolver.resolveAll("command");
|
|
@@ -28843,6 +29012,7 @@ function createTools(ctx) {
|
|
|
28843
29012
|
goop_checkpoint: createGoopCheckpointTool(ctx),
|
|
28844
29013
|
goop_skill: createGoopSkillTool(ctx),
|
|
28845
29014
|
goop_delegate: createGoopDelegateTool(ctx),
|
|
29015
|
+
goop_reference: createGoopReferenceTool(ctx),
|
|
28846
29016
|
goop_setup: createGoopSetupTool(ctx),
|
|
28847
29017
|
slashcommand: createSlashcommandTool(ctx),
|
|
28848
29018
|
memory_save: createMemorySaveTool(ctx),
|
|
@@ -29550,36 +29720,564 @@ function createMemoryContextBuilder(memoryManager, config3) {
|
|
|
29550
29720
|
return new MemoryContextBuilder(memoryManager, config3);
|
|
29551
29721
|
}
|
|
29552
29722
|
|
|
29723
|
+
// src/features/enforcement/phase-context.ts
|
|
29724
|
+
var DELEGATION_EXAMPLE = `
|
|
29725
|
+
Use the task tool for delegation:
|
|
29726
|
+
\`\`\`
|
|
29727
|
+
task({
|
|
29728
|
+
subagent_type: "goop-executor", // or other agent type
|
|
29729
|
+
description: "Brief task description",
|
|
29730
|
+
prompt: \`
|
|
29731
|
+
## TASK
|
|
29732
|
+
[Specific, atomic goal]
|
|
29733
|
+
|
|
29734
|
+
## FILES
|
|
29735
|
+
- path/to/file.ts (modify/create)
|
|
29736
|
+
|
|
29737
|
+
## REQUIREMENTS
|
|
29738
|
+
[From SPEC.md]
|
|
29739
|
+
|
|
29740
|
+
## ACCEPTANCE
|
|
29741
|
+
[How to verify completion]
|
|
29742
|
+
\`
|
|
29743
|
+
})
|
|
29744
|
+
\`\`\`
|
|
29745
|
+
NOTE: Use "task" tool, NOT "delegate" tool for subagent work.
|
|
29746
|
+
`.trim();
|
|
29747
|
+
var PHASE_ENFORCEMENT = {
|
|
29748
|
+
idle: {
|
|
29749
|
+
phaseName: "IDLE",
|
|
29750
|
+
mustDo: [
|
|
29751
|
+
"Use /goop-plan to start a new feature",
|
|
29752
|
+
"Use /goop-status to check current state"
|
|
29753
|
+
],
|
|
29754
|
+
mustNotDo: [
|
|
29755
|
+
"Write implementation code without a plan",
|
|
29756
|
+
"Skip the planning phase"
|
|
29757
|
+
],
|
|
29758
|
+
requiredDocuments: []
|
|
29759
|
+
},
|
|
29760
|
+
plan: {
|
|
29761
|
+
phaseName: "PLAN",
|
|
29762
|
+
mustDo: [
|
|
29763
|
+
"Ask clarifying questions to understand requirements",
|
|
29764
|
+
"Create SPEC.md with must-haves, nice-to-haves, out-of-scope",
|
|
29765
|
+
"Define clear success criteria",
|
|
29766
|
+
"Get user confirmation before proceeding to research/execute",
|
|
29767
|
+
"Call goop_status() to verify current state"
|
|
29768
|
+
],
|
|
29769
|
+
mustNotDo: [
|
|
29770
|
+
"Write ANY implementation code",
|
|
29771
|
+
"Create source files (only .goopspec/ documents)",
|
|
29772
|
+
"Skip requirement gathering",
|
|
29773
|
+
"Proceed without user confirmation",
|
|
29774
|
+
"Use write/edit tools on src/ files"
|
|
29775
|
+
],
|
|
29776
|
+
requiredDocuments: ["SPEC.md"]
|
|
29777
|
+
},
|
|
29778
|
+
research: {
|
|
29779
|
+
phaseName: "RESEARCH",
|
|
29780
|
+
mustDo: [
|
|
29781
|
+
"Read SPEC.md to understand requirements",
|
|
29782
|
+
"Research implementation approaches",
|
|
29783
|
+
"Create RESEARCH.md with findings",
|
|
29784
|
+
"Document trade-offs and recommendations",
|
|
29785
|
+
"Search memory for similar past work"
|
|
29786
|
+
],
|
|
29787
|
+
mustNotDo: [
|
|
29788
|
+
"Write implementation code",
|
|
29789
|
+
"Modify source files",
|
|
29790
|
+
"Skip documenting findings",
|
|
29791
|
+
"Make architectural decisions without documenting"
|
|
29792
|
+
],
|
|
29793
|
+
requiredDocuments: ["SPEC.md", "RESEARCH.md"]
|
|
29794
|
+
},
|
|
29795
|
+
specify: {
|
|
29796
|
+
phaseName: "SPECIFY",
|
|
29797
|
+
mustDo: [
|
|
29798
|
+
"Create BLUEPRINT.md with wave-based execution plan",
|
|
29799
|
+
"Map all must-haves to specific tasks",
|
|
29800
|
+
"Define verification steps for each task",
|
|
29801
|
+
"Get user confirmation to lock specification",
|
|
29802
|
+
"Call goop_checkpoint() to save state"
|
|
29803
|
+
],
|
|
29804
|
+
mustNotDo: [
|
|
29805
|
+
"Write implementation code",
|
|
29806
|
+
"Proceed without locked specification",
|
|
29807
|
+
"Skip task decomposition",
|
|
29808
|
+
"Create vague or incomplete tasks"
|
|
29809
|
+
],
|
|
29810
|
+
requiredDocuments: ["SPEC.md", "BLUEPRINT.md"]
|
|
29811
|
+
},
|
|
29812
|
+
execute: {
|
|
29813
|
+
phaseName: "EXECUTE",
|
|
29814
|
+
mustDo: [
|
|
29815
|
+
"DELEGATE all code work to executor agents using task() tool",
|
|
29816
|
+
"Track progress in CHRONICLE.md",
|
|
29817
|
+
"Follow wave order (complete wave N before wave N+1)",
|
|
29818
|
+
"Verify each task completion before moving on",
|
|
29819
|
+
"Save checkpoints at wave boundaries",
|
|
29820
|
+
"Update ADL.md with any deviations"
|
|
29821
|
+
],
|
|
29822
|
+
mustNotDo: [
|
|
29823
|
+
"Write code directly - ALWAYS delegate to subagents",
|
|
29824
|
+
"Use 'delegate' tool - use 'task' tool instead",
|
|
29825
|
+
"Skip verification steps",
|
|
29826
|
+
"Ignore test failures",
|
|
29827
|
+
"Modify files outside BLUEPRINT.md scope"
|
|
29828
|
+
],
|
|
29829
|
+
requiredDocuments: ["SPEC.md", "BLUEPRINT.md", "CHRONICLE.md"],
|
|
29830
|
+
delegationReminder: DELEGATION_EXAMPLE
|
|
29831
|
+
},
|
|
29832
|
+
accept: {
|
|
29833
|
+
phaseName: "ACCEPT",
|
|
29834
|
+
mustDo: [
|
|
29835
|
+
"Verify ALL must-haves from SPEC.md are complete",
|
|
29836
|
+
"Run all tests and ensure they pass",
|
|
29837
|
+
"Check for any deviations in ADL.md",
|
|
29838
|
+
"Get explicit user acceptance",
|
|
29839
|
+
"Save final checkpoint"
|
|
29840
|
+
],
|
|
29841
|
+
mustNotDo: [
|
|
29842
|
+
"Mark complete without verification",
|
|
29843
|
+
"Skip user confirmation",
|
|
29844
|
+
"Ignore failing tests",
|
|
29845
|
+
"Proceed if must-haves are missing"
|
|
29846
|
+
],
|
|
29847
|
+
requiredDocuments: ["SPEC.md", "BLUEPRINT.md", "CHRONICLE.md"]
|
|
29848
|
+
}
|
|
29849
|
+
};
|
|
29850
|
+
function buildPhaseEnforcement(phase, _state) {
|
|
29851
|
+
const rules = PHASE_ENFORCEMENT[phase];
|
|
29852
|
+
if (!rules) {
|
|
29853
|
+
return "";
|
|
29854
|
+
}
|
|
29855
|
+
const lines = [
|
|
29856
|
+
`## PHASE ENFORCEMENT: ${rules.phaseName}`,
|
|
29857
|
+
"",
|
|
29858
|
+
"### MUST DO:"
|
|
29859
|
+
];
|
|
29860
|
+
for (const item of rules.mustDo) {
|
|
29861
|
+
lines.push(`- ${item}`);
|
|
29862
|
+
}
|
|
29863
|
+
lines.push("", "### MUST NOT DO:");
|
|
29864
|
+
for (const item of rules.mustNotDo) {
|
|
29865
|
+
lines.push(`- ${item}`);
|
|
29866
|
+
}
|
|
29867
|
+
if (rules.requiredDocuments.length > 0) {
|
|
29868
|
+
lines.push("", "### REQUIRED DOCUMENTS:");
|
|
29869
|
+
for (const doc3 of rules.requiredDocuments) {
|
|
29870
|
+
lines.push(`- [ ] ${doc3}`);
|
|
29871
|
+
}
|
|
29872
|
+
}
|
|
29873
|
+
if (rules.delegationReminder) {
|
|
29874
|
+
lines.push("", "### DELEGATION (CRITICAL):", "", rules.delegationReminder);
|
|
29875
|
+
}
|
|
29876
|
+
return lines.join(`
|
|
29877
|
+
`);
|
|
29878
|
+
}
|
|
29879
|
+
function buildStateContext(state) {
|
|
29880
|
+
const workflow = state.workflow;
|
|
29881
|
+
const execution = state.execution;
|
|
29882
|
+
const lines = [
|
|
29883
|
+
"## CURRENT STATE",
|
|
29884
|
+
"",
|
|
29885
|
+
`**Phase:** ${workflow.phase}`,
|
|
29886
|
+
`**Mode:** ${workflow.mode}`,
|
|
29887
|
+
`**Spec Locked:** ${workflow.specLocked ? "Yes" : "No"}`,
|
|
29888
|
+
`**Acceptance Confirmed:** ${workflow.acceptanceConfirmed ? "Yes" : "No"}`
|
|
29889
|
+
];
|
|
29890
|
+
if (workflow.totalWaves > 0) {
|
|
29891
|
+
lines.push(`**Wave Progress:** ${workflow.currentWave}/${workflow.totalWaves}`);
|
|
29892
|
+
}
|
|
29893
|
+
if (execution.activeCheckpointId) {
|
|
29894
|
+
lines.push(`**Active Checkpoint:** ${execution.activeCheckpointId}`);
|
|
29895
|
+
}
|
|
29896
|
+
if (execution.pendingTasks.length > 0) {
|
|
29897
|
+
lines.push(`**Pending Tasks:** ${execution.pendingTasks.length}`);
|
|
29898
|
+
}
|
|
29899
|
+
return lines.join(`
|
|
29900
|
+
`);
|
|
29901
|
+
}
|
|
29902
|
+
function buildEnforcementContext(state) {
|
|
29903
|
+
const stateContext = buildStateContext(state);
|
|
29904
|
+
const phaseEnforcement = buildPhaseEnforcement(state.workflow.phase, state);
|
|
29905
|
+
if (!phaseEnforcement) {
|
|
29906
|
+
return stateContext;
|
|
29907
|
+
}
|
|
29908
|
+
return `${stateContext}
|
|
29909
|
+
|
|
29910
|
+
${phaseEnforcement}`;
|
|
29911
|
+
}
|
|
29912
|
+
// src/features/enforcement/scaffolder.ts
|
|
29913
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
29914
|
+
import { join as join8, dirname as dirname4 } from "path";
|
|
29915
|
+
|
|
29916
|
+
// src/shared/template-engine.ts
|
|
29917
|
+
function renderTemplate(template, context) {
|
|
29918
|
+
let result = template;
|
|
29919
|
+
result = result.replace(/\{\{([^#^/][^}]*)\}\}/g, (match, key) => {
|
|
29920
|
+
const trimmedKey = key.trim();
|
|
29921
|
+
const value = getNestedValue(context, trimmedKey);
|
|
29922
|
+
if (value === undefined || value === null) {
|
|
29923
|
+
return match;
|
|
29924
|
+
}
|
|
29925
|
+
if (Array.isArray(value)) {
|
|
29926
|
+
return value.join(", ");
|
|
29927
|
+
}
|
|
29928
|
+
return String(value);
|
|
29929
|
+
});
|
|
29930
|
+
result = result.replace(/\{\{#(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, (_match, key, content) => {
|
|
29931
|
+
const value = getNestedValue(context, key);
|
|
29932
|
+
if (!value) {
|
|
29933
|
+
return "";
|
|
29934
|
+
}
|
|
29935
|
+
if (Array.isArray(value)) {
|
|
29936
|
+
return value.map((item) => {
|
|
29937
|
+
if (typeof item === "object") {
|
|
29938
|
+
return renderTemplate(content, item);
|
|
29939
|
+
}
|
|
29940
|
+
return content.replace(/\{\{\.\}\}/g, String(item));
|
|
29941
|
+
}).join("");
|
|
29942
|
+
}
|
|
29943
|
+
return renderTemplate(content, context);
|
|
29944
|
+
});
|
|
29945
|
+
result = result.replace(/\{\{\^(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, (_match, key, content) => {
|
|
29946
|
+
const value = getNestedValue(context, key);
|
|
29947
|
+
if (!value || Array.isArray(value) && value.length === 0) {
|
|
29948
|
+
return renderTemplate(content, context);
|
|
29949
|
+
}
|
|
29950
|
+
return "";
|
|
29951
|
+
});
|
|
29952
|
+
return result;
|
|
29953
|
+
}
|
|
29954
|
+
function getNestedValue(context, path2) {
|
|
29955
|
+
const parts = path2.split(".");
|
|
29956
|
+
let current = context;
|
|
29957
|
+
for (const part of parts) {
|
|
29958
|
+
if (current === null || current === undefined) {
|
|
29959
|
+
return;
|
|
29960
|
+
}
|
|
29961
|
+
if (typeof current === "object") {
|
|
29962
|
+
current = current[part];
|
|
29963
|
+
} else {
|
|
29964
|
+
return;
|
|
29965
|
+
}
|
|
29966
|
+
}
|
|
29967
|
+
return current;
|
|
29968
|
+
}
|
|
29969
|
+
function createDefaultContext(projectName, phaseName, additionalContext = {}) {
|
|
29970
|
+
const now = new Date;
|
|
29971
|
+
return {
|
|
29972
|
+
project_name: projectName,
|
|
29973
|
+
phase_name: phaseName,
|
|
29974
|
+
version: "1.0.0",
|
|
29975
|
+
created_date: now.toISOString().split("T")[0],
|
|
29976
|
+
updated_date: now.toISOString().split("T")[0],
|
|
29977
|
+
status: "DRAFT",
|
|
29978
|
+
mode: "standard",
|
|
29979
|
+
vision: "[Vision to be defined]",
|
|
29980
|
+
why: "[Motivation to be defined]",
|
|
29981
|
+
goal: "[Goal to be defined]",
|
|
29982
|
+
approach: "[Approach to be defined]",
|
|
29983
|
+
must_haves: [],
|
|
29984
|
+
nice_to_haves: [],
|
|
29985
|
+
out_of_scope: [],
|
|
29986
|
+
acceptance_criteria: [],
|
|
29987
|
+
always: [],
|
|
29988
|
+
ask_first: [],
|
|
29989
|
+
never: [],
|
|
29990
|
+
runtime: "Bun",
|
|
29991
|
+
language: "TypeScript",
|
|
29992
|
+
framework: "[Framework]",
|
|
29993
|
+
database: "[Database]",
|
|
29994
|
+
testing: "bun:test",
|
|
29995
|
+
file_naming: "kebab-case",
|
|
29996
|
+
component_naming: "PascalCase",
|
|
29997
|
+
function_naming: "camelCase",
|
|
29998
|
+
commit_format: "conventional",
|
|
29999
|
+
...additionalContext
|
|
30000
|
+
};
|
|
30001
|
+
}
|
|
30002
|
+
|
|
30003
|
+
// src/features/enforcement/scaffolder.ts
|
|
30004
|
+
var DOCUMENT_CONFIGS = {
|
|
30005
|
+
spec: {
|
|
30006
|
+
templateName: "spec.md",
|
|
30007
|
+
fileName: "SPEC.md",
|
|
30008
|
+
requiredInPhases: ["plan", "research", "specify", "execute", "accept"]
|
|
30009
|
+
},
|
|
30010
|
+
blueprint: {
|
|
30011
|
+
templateName: "blueprint.md",
|
|
30012
|
+
fileName: "BLUEPRINT.md",
|
|
30013
|
+
requiredInPhases: ["specify", "execute", "accept"]
|
|
30014
|
+
},
|
|
30015
|
+
chronicle: {
|
|
30016
|
+
templateName: "chronicle.md",
|
|
30017
|
+
fileName: "CHRONICLE.md",
|
|
30018
|
+
requiredInPhases: ["plan", "research", "specify", "execute", "accept"]
|
|
30019
|
+
},
|
|
30020
|
+
research: {
|
|
30021
|
+
templateName: "research.md",
|
|
30022
|
+
fileName: "RESEARCH.md",
|
|
30023
|
+
requiredInPhases: ["research", "specify"]
|
|
30024
|
+
}
|
|
30025
|
+
};
|
|
30026
|
+
function getTemplatesDir(ctx) {
|
|
30027
|
+
const projectTemplates = join8(ctx.input.directory, "templates");
|
|
30028
|
+
if (existsSync10(projectTemplates)) {
|
|
30029
|
+
return projectTemplates;
|
|
30030
|
+
}
|
|
30031
|
+
const resolver = ctx.resolver;
|
|
30032
|
+
const templateDir = resolver.getDirectory("template");
|
|
30033
|
+
if (templateDir && existsSync10(templateDir)) {
|
|
30034
|
+
return templateDir;
|
|
30035
|
+
}
|
|
30036
|
+
const devTemplates = join8(dirname4(import.meta.dir), "..", "..", "templates");
|
|
30037
|
+
if (existsSync10(devTemplates)) {
|
|
30038
|
+
return devTemplates;
|
|
30039
|
+
}
|
|
30040
|
+
throw new Error("Templates directory not found");
|
|
30041
|
+
}
|
|
30042
|
+
function loadTemplate(ctx, templateName) {
|
|
30043
|
+
try {
|
|
30044
|
+
const templatesDir = getTemplatesDir(ctx);
|
|
30045
|
+
const templatePath = join8(templatesDir, templateName);
|
|
30046
|
+
if (!existsSync10(templatePath)) {
|
|
30047
|
+
log(`Template not found: ${templatePath}`);
|
|
30048
|
+
return null;
|
|
30049
|
+
}
|
|
30050
|
+
return readFileSync8(templatePath, "utf-8");
|
|
30051
|
+
} catch (error92) {
|
|
30052
|
+
logError(`Failed to load template: ${templateName}`, error92);
|
|
30053
|
+
return null;
|
|
30054
|
+
}
|
|
30055
|
+
}
|
|
30056
|
+
async function scaffoldPhaseDocuments(ctx, phaseName, phase, additionalContext = {}) {
|
|
30057
|
+
const result = {
|
|
30058
|
+
success: true,
|
|
30059
|
+
phaseDir: "",
|
|
30060
|
+
documentsCreated: [],
|
|
30061
|
+
documentsSkipped: [],
|
|
30062
|
+
errors: []
|
|
30063
|
+
};
|
|
30064
|
+
try {
|
|
30065
|
+
const goopspecDir = getProjectGoopspecDir(ctx.input.directory);
|
|
30066
|
+
const phaseDir = join8(goopspecDir, "phases", sanitizePhaseName2(phaseName));
|
|
30067
|
+
result.phaseDir = phaseDir;
|
|
30068
|
+
if (!existsSync10(phaseDir)) {
|
|
30069
|
+
mkdirSync5(phaseDir, { recursive: true });
|
|
30070
|
+
log(`Created phase directory: ${phaseDir}`);
|
|
30071
|
+
}
|
|
30072
|
+
const projectName = ctx.config.projectName || ctx.input.project.name || "unnamed";
|
|
30073
|
+
const templateContext = createDefaultContext(projectName, phaseName, {
|
|
30074
|
+
current_phase: phase,
|
|
30075
|
+
...additionalContext
|
|
30076
|
+
});
|
|
30077
|
+
for (const [docType, config3] of Object.entries(DOCUMENT_CONFIGS)) {
|
|
30078
|
+
const docPath = join8(phaseDir, config3.fileName);
|
|
30079
|
+
if (existsSync10(docPath)) {
|
|
30080
|
+
result.documentsSkipped.push(config3.fileName);
|
|
30081
|
+
log(`Document already exists, skipping: ${config3.fileName}`);
|
|
30082
|
+
continue;
|
|
30083
|
+
}
|
|
30084
|
+
if (!config3.requiredInPhases.includes(phase)) {
|
|
30085
|
+
continue;
|
|
30086
|
+
}
|
|
30087
|
+
const template = loadTemplate(ctx, config3.templateName);
|
|
30088
|
+
if (!template) {
|
|
30089
|
+
const minimalContent = createMinimalDocument(docType, templateContext);
|
|
30090
|
+
writeFileSync4(docPath, minimalContent, "utf-8");
|
|
30091
|
+
result.documentsCreated.push(config3.fileName);
|
|
30092
|
+
log(`Created minimal document (no template): ${config3.fileName}`);
|
|
30093
|
+
continue;
|
|
30094
|
+
}
|
|
30095
|
+
const content = renderTemplate(template, templateContext);
|
|
30096
|
+
writeFileSync4(docPath, content, "utf-8");
|
|
30097
|
+
result.documentsCreated.push(config3.fileName);
|
|
30098
|
+
log(`Created document: ${config3.fileName}`);
|
|
30099
|
+
}
|
|
30100
|
+
return result;
|
|
30101
|
+
} catch (error92) {
|
|
30102
|
+
result.success = false;
|
|
30103
|
+
result.errors.push(error92 instanceof Error ? error92.message : String(error92));
|
|
30104
|
+
logError("Failed to scaffold phase documents", error92);
|
|
30105
|
+
return result;
|
|
30106
|
+
}
|
|
30107
|
+
}
|
|
30108
|
+
function sanitizePhaseName2(name) {
|
|
30109
|
+
return name.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
30110
|
+
}
|
|
30111
|
+
function createMinimalDocument(docType, context) {
|
|
30112
|
+
const projectName = context.project_name || "Project";
|
|
30113
|
+
const phaseName = context.phase_name || "Phase";
|
|
30114
|
+
const date9 = context.created_date || new Date().toISOString().split("T")[0];
|
|
30115
|
+
switch (docType) {
|
|
30116
|
+
case "spec":
|
|
30117
|
+
return `# SPEC: ${projectName}
|
|
30118
|
+
|
|
30119
|
+
**Version:** 1.0.0
|
|
30120
|
+
**Created:** ${date9}
|
|
30121
|
+
**Status:** DRAFT
|
|
30122
|
+
|
|
30123
|
+
---
|
|
30124
|
+
|
|
30125
|
+
## Vision
|
|
30126
|
+
|
|
30127
|
+
[Describe what you're building and why]
|
|
30128
|
+
|
|
30129
|
+
---
|
|
30130
|
+
|
|
30131
|
+
## Must-Haves
|
|
30132
|
+
|
|
30133
|
+
- [ ] [Requirement 1]
|
|
30134
|
+
- [ ] [Requirement 2]
|
|
30135
|
+
|
|
30136
|
+
---
|
|
30137
|
+
|
|
30138
|
+
## Out of Scope
|
|
30139
|
+
|
|
30140
|
+
- [Item 1]
|
|
30141
|
+
|
|
30142
|
+
---
|
|
30143
|
+
|
|
30144
|
+
## Success Criteria
|
|
30145
|
+
|
|
30146
|
+
1. [Criterion 1]
|
|
30147
|
+
2. [Criterion 2]
|
|
30148
|
+
|
|
30149
|
+
---
|
|
30150
|
+
|
|
30151
|
+
*GoopSpec v0.1.0*
|
|
30152
|
+
`;
|
|
30153
|
+
case "blueprint":
|
|
30154
|
+
return `# BLUEPRINT: ${projectName}
|
|
30155
|
+
|
|
30156
|
+
**Spec Version:** 1.0.0
|
|
30157
|
+
**Created:** ${date9}
|
|
30158
|
+
**Mode:** standard
|
|
30159
|
+
|
|
30160
|
+
---
|
|
30161
|
+
|
|
30162
|
+
## Overview
|
|
30163
|
+
|
|
30164
|
+
**Goal:** [Define the goal]
|
|
30165
|
+
|
|
30166
|
+
**Waves:** 0
|
|
30167
|
+
**Tasks:** 0
|
|
30168
|
+
|
|
30169
|
+
---
|
|
30170
|
+
|
|
30171
|
+
## Waves
|
|
30172
|
+
|
|
30173
|
+
[Define waves and tasks here]
|
|
30174
|
+
|
|
30175
|
+
---
|
|
30176
|
+
|
|
30177
|
+
*GoopSpec v0.1.0*
|
|
30178
|
+
`;
|
|
30179
|
+
case "chronicle":
|
|
30180
|
+
return `# CHRONICLE: ${projectName}
|
|
30181
|
+
|
|
30182
|
+
**Last Updated:** ${date9}
|
|
30183
|
+
**Current Phase:** ${phaseName}
|
|
30184
|
+
|
|
30185
|
+
---
|
|
30186
|
+
|
|
30187
|
+
## Status
|
|
30188
|
+
|
|
30189
|
+
**Position:** Starting
|
|
30190
|
+
|
|
30191
|
+
| Metric | Value |
|
|
30192
|
+
|--------|-------|
|
|
30193
|
+
| Waves Completed | 0/0 |
|
|
30194
|
+
| Tasks Done | 0 |
|
|
30195
|
+
|
|
30196
|
+
---
|
|
30197
|
+
|
|
30198
|
+
## Recent Activity
|
|
30199
|
+
|
|
30200
|
+
[Activity will be logged here]
|
|
30201
|
+
|
|
30202
|
+
---
|
|
30203
|
+
|
|
30204
|
+
*GoopSpec v0.1.0*
|
|
30205
|
+
`;
|
|
30206
|
+
case "research":
|
|
30207
|
+
return `# RESEARCH: ${projectName}
|
|
30208
|
+
|
|
30209
|
+
**Created:** ${date9}
|
|
30210
|
+
**Phase:** ${phaseName}
|
|
30211
|
+
|
|
30212
|
+
---
|
|
30213
|
+
|
|
30214
|
+
## Research Goals
|
|
30215
|
+
|
|
30216
|
+
[What are you researching?]
|
|
30217
|
+
|
|
30218
|
+
---
|
|
30219
|
+
|
|
30220
|
+
## Findings
|
|
30221
|
+
|
|
30222
|
+
[Document findings here]
|
|
30223
|
+
|
|
30224
|
+
---
|
|
30225
|
+
|
|
30226
|
+
## Recommendations
|
|
30227
|
+
|
|
30228
|
+
[Recommendations based on research]
|
|
30229
|
+
|
|
30230
|
+
---
|
|
30231
|
+
|
|
30232
|
+
*GoopSpec v0.1.0*
|
|
30233
|
+
`;
|
|
30234
|
+
default:
|
|
30235
|
+
return `# ${String(docType).toUpperCase()}
|
|
30236
|
+
|
|
30237
|
+
Created: ${date9}
|
|
30238
|
+
`;
|
|
30239
|
+
}
|
|
30240
|
+
}
|
|
30241
|
+
// src/features/enforcement/validators.ts
|
|
30242
|
+
var EXCLUDED_EXTENSIONS = new Set([".md", ".json"]);
|
|
29553
30243
|
// src/hooks/system-transform.ts
|
|
29554
30244
|
function createSystemTransformHook(ctx) {
|
|
29555
30245
|
return async (input, output) => {
|
|
29556
|
-
if (!ctx.memoryManager || ctx.config.memory?.injection?.enabled === false) {
|
|
29557
|
-
return output;
|
|
29558
|
-
}
|
|
29559
30246
|
log("System transform hook triggered", {
|
|
29560
30247
|
agent: input.agent,
|
|
29561
30248
|
sessionID: input.sessionID
|
|
29562
30249
|
});
|
|
29563
30250
|
try {
|
|
29564
|
-
const
|
|
29565
|
-
|
|
29566
|
-
|
|
29567
|
-
|
|
29568
|
-
|
|
29569
|
-
|
|
29570
|
-
|
|
29571
|
-
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
|
|
30251
|
+
const state = ctx.stateManager.getState();
|
|
30252
|
+
const enforcementContext = buildEnforcementContext(state);
|
|
30253
|
+
let memoryContext = "";
|
|
30254
|
+
if (ctx.memoryManager && ctx.config.memory?.injection?.enabled !== false) {
|
|
30255
|
+
const builder = createMemoryContextBuilder(ctx.memoryManager, {
|
|
30256
|
+
budgetTokens: ctx.config.memory?.injection?.budgetTokens ?? 800,
|
|
30257
|
+
format: ctx.config.memory?.injection?.format ?? "structured",
|
|
30258
|
+
priorityTypes: ctx.config.memory?.injection?.priorityTypes ?? ["decision", "observation", "todo"],
|
|
30259
|
+
includeDecisions: true,
|
|
30260
|
+
includeRecentActivity: true
|
|
30261
|
+
});
|
|
30262
|
+
const currentPhase = state.workflow.phase;
|
|
30263
|
+
if (currentPhase) {
|
|
30264
|
+
memoryContext = await builder.buildPhaseContext(currentPhase);
|
|
30265
|
+
} else {
|
|
30266
|
+
memoryContext = await builder.buildRecentContext(10);
|
|
30267
|
+
}
|
|
29577
30268
|
}
|
|
29578
|
-
if (!memoryContext || memoryContext.trim().length === 0) {
|
|
29579
|
-
log("No memory context to inject");
|
|
30269
|
+
if (!enforcementContext && (!memoryContext || memoryContext.trim().length === 0)) {
|
|
30270
|
+
log("No enforcement or memory context to inject");
|
|
29580
30271
|
return output;
|
|
29581
30272
|
}
|
|
29582
|
-
|
|
30273
|
+
let enhancedSystem = output.system;
|
|
30274
|
+
if (enforcementContext.trim().length > 0) {
|
|
30275
|
+
enhancedSystem = `${enhancedSystem}
|
|
30276
|
+
|
|
30277
|
+
${enforcementContext}`;
|
|
30278
|
+
}
|
|
30279
|
+
if (memoryContext.trim().length > 0) {
|
|
30280
|
+
enhancedSystem = `${enhancedSystem}
|
|
29583
30281
|
|
|
29584
30282
|
## Persistent Memory Context
|
|
29585
30283
|
|
|
@@ -29588,28 +30286,159 @@ The following memories are relevant to this session. Use them to maintain contin
|
|
|
29588
30286
|
${memoryContext}
|
|
29589
30287
|
|
|
29590
30288
|
Use the memory tools (memory_save, memory_search, memory_note, memory_decision) to store and retrieve information for future sessions.`;
|
|
29591
|
-
|
|
30289
|
+
}
|
|
30290
|
+
log("System context injected", {
|
|
29592
30291
|
originalLength: output.system.length,
|
|
29593
|
-
|
|
30292
|
+
enforcementLength: enforcementContext.length,
|
|
30293
|
+
memoryLength: memoryContext.length,
|
|
29594
30294
|
enhancedLength: enhancedSystem.length
|
|
29595
30295
|
});
|
|
29596
30296
|
return {
|
|
29597
30297
|
system: enhancedSystem
|
|
29598
30298
|
};
|
|
29599
30299
|
} catch (error92) {
|
|
29600
|
-
logError("Failed to inject
|
|
30300
|
+
logError("Failed to inject system context", error92);
|
|
29601
30301
|
return output;
|
|
29602
30302
|
}
|
|
29603
30303
|
};
|
|
29604
30304
|
}
|
|
29605
30305
|
|
|
30306
|
+
// src/hooks/command-processor.ts
|
|
30307
|
+
import { join as join9 } from "path";
|
|
30308
|
+
var COMMAND_PHASES = {
|
|
30309
|
+
"goop-plan": "plan",
|
|
30310
|
+
"goop-research": "research",
|
|
30311
|
+
"goop-specify": "specify",
|
|
30312
|
+
"goop-execute": "execute",
|
|
30313
|
+
"goop-accept": "accept"
|
|
30314
|
+
};
|
|
30315
|
+
function createCommandProcessor(ctx) {
|
|
30316
|
+
return {
|
|
30317
|
+
"tool.execute.after": async (input, output) => {
|
|
30318
|
+
if (input.tool !== "slashcommand") {
|
|
30319
|
+
return;
|
|
30320
|
+
}
|
|
30321
|
+
const commandText = getCommandText(output);
|
|
30322
|
+
const parsed = commandText ? parseCommandText(commandText) : null;
|
|
30323
|
+
if (!parsed) {
|
|
30324
|
+
return;
|
|
30325
|
+
}
|
|
30326
|
+
const phase = COMMAND_PHASES[parsed.name];
|
|
30327
|
+
if (!phase) {
|
|
30328
|
+
return;
|
|
30329
|
+
}
|
|
30330
|
+
const transitioned = ctx.stateManager.transitionPhase(phase);
|
|
30331
|
+
let scaffoldResult = null;
|
|
30332
|
+
let phaseName = "";
|
|
30333
|
+
if (parsed.name === "goop-plan") {
|
|
30334
|
+
phaseName = parsed.args || "plan";
|
|
30335
|
+
scaffoldResult = await scaffoldPhaseDocuments(ctx, phaseName, "plan");
|
|
30336
|
+
}
|
|
30337
|
+
if (!transitioned) {
|
|
30338
|
+
logError(`Command processor failed to transition to ${phase}`);
|
|
30339
|
+
}
|
|
30340
|
+
if (scaffoldResult && !scaffoldResult.success) {
|
|
30341
|
+
logError("Command processor failed to scaffold phase documents", scaffoldResult.errors);
|
|
30342
|
+
}
|
|
30343
|
+
const files = scaffoldResult ? scaffoldResult.documentsCreated.map((doc3) => join9(scaffoldResult.phaseDir, doc3)) : undefined;
|
|
30344
|
+
const actionParts = [`Transitioned to ${phase}`];
|
|
30345
|
+
if (scaffoldResult) {
|
|
30346
|
+
const scaffoldStatus = scaffoldResult.success ? "scaffolded" : "failed to scaffold";
|
|
30347
|
+
const docCount = scaffoldResult.documentsCreated.length;
|
|
30348
|
+
const namePart = phaseName ? ` for phase "${phaseName}"` : "";
|
|
30349
|
+
actionParts.push(`${scaffoldStatus} ${docCount} documents${namePart}`);
|
|
30350
|
+
if (!scaffoldResult.success && scaffoldResult.errors.length > 0) {
|
|
30351
|
+
actionParts.push(`errors: ${scaffoldResult.errors.join("; ")}`);
|
|
30352
|
+
}
|
|
30353
|
+
}
|
|
30354
|
+
const adlEntry = {
|
|
30355
|
+
timestamp: new Date().toISOString(),
|
|
30356
|
+
type: "observation",
|
|
30357
|
+
description: `Slash command "/${parsed.name}" processed`,
|
|
30358
|
+
action: transitioned ? actionParts.join("; ") : `Transition to ${phase} rejected`,
|
|
30359
|
+
files
|
|
30360
|
+
};
|
|
30361
|
+
ctx.stateManager.appendADL(adlEntry);
|
|
30362
|
+
log("Command processor handled slash command", {
|
|
30363
|
+
command: parsed.name,
|
|
30364
|
+
phase,
|
|
30365
|
+
transitioned,
|
|
30366
|
+
phaseName: phaseName || undefined
|
|
30367
|
+
});
|
|
30368
|
+
}
|
|
30369
|
+
};
|
|
30370
|
+
}
|
|
30371
|
+
function getCommandText(output) {
|
|
30372
|
+
const metadataCommand = extractCommandFromMetadata(output.metadata);
|
|
30373
|
+
if (metadataCommand) {
|
|
30374
|
+
return metadataCommand;
|
|
30375
|
+
}
|
|
30376
|
+
return extractCommandFromOutput(output.output);
|
|
30377
|
+
}
|
|
30378
|
+
function extractCommandFromMetadata(metadata) {
|
|
30379
|
+
if (!metadata || typeof metadata !== "object") {
|
|
30380
|
+
return null;
|
|
30381
|
+
}
|
|
30382
|
+
const record3 = metadata;
|
|
30383
|
+
if (typeof record3.command === "string") {
|
|
30384
|
+
return record3.command;
|
|
30385
|
+
}
|
|
30386
|
+
const args = record3.args;
|
|
30387
|
+
if (args && typeof args === "object") {
|
|
30388
|
+
const argsRecord = args;
|
|
30389
|
+
if (typeof argsRecord.command === "string") {
|
|
30390
|
+
return argsRecord.command;
|
|
30391
|
+
}
|
|
30392
|
+
}
|
|
30393
|
+
return null;
|
|
30394
|
+
}
|
|
30395
|
+
function extractCommandFromOutput(output) {
|
|
30396
|
+
if (!output) {
|
|
30397
|
+
return null;
|
|
30398
|
+
}
|
|
30399
|
+
const match = output.match(/^#\s*\/([^\s]+)\s+Command/m);
|
|
30400
|
+
if (!match) {
|
|
30401
|
+
return null;
|
|
30402
|
+
}
|
|
30403
|
+
return match[1];
|
|
30404
|
+
}
|
|
30405
|
+
function parseCommandText(raw) {
|
|
30406
|
+
const trimmed = raw.trim();
|
|
30407
|
+
if (!trimmed) {
|
|
30408
|
+
return null;
|
|
30409
|
+
}
|
|
30410
|
+
const normalized = trimmed.replace(/^\//, "");
|
|
30411
|
+
const [name, ...rest] = normalized.split(/\s+/);
|
|
30412
|
+
if (!name) {
|
|
30413
|
+
return null;
|
|
30414
|
+
}
|
|
30415
|
+
return {
|
|
30416
|
+
name: name.toLowerCase(),
|
|
30417
|
+
args: rest.join(" ").trim()
|
|
30418
|
+
};
|
|
30419
|
+
}
|
|
30420
|
+
|
|
29606
30421
|
// src/hooks/index.ts
|
|
29607
30422
|
function createHooks(ctx) {
|
|
30423
|
+
const toolLifecycleHooks = createToolLifecycleHooks(ctx);
|
|
30424
|
+
const commandProcessorHooks = createCommandProcessor(ctx);
|
|
30425
|
+
const toolExecuteAfterHooks = [
|
|
30426
|
+
toolLifecycleHooks["tool.execute.after"],
|
|
30427
|
+
commandProcessorHooks["tool.execute.after"]
|
|
30428
|
+
].filter((hook) => Boolean(hook));
|
|
29608
30429
|
const hooks = {
|
|
29609
30430
|
event: createEventHandler(ctx),
|
|
29610
30431
|
"chat.message": createChatMessageHook(ctx),
|
|
29611
|
-
...
|
|
30432
|
+
...toolLifecycleHooks,
|
|
30433
|
+
...commandProcessorHooks
|
|
29612
30434
|
};
|
|
30435
|
+
if (toolExecuteAfterHooks.length > 0) {
|
|
30436
|
+
hooks["tool.execute.after"] = async (input, output) => {
|
|
30437
|
+
for (const hook of toolExecuteAfterHooks) {
|
|
30438
|
+
await hook(input, output);
|
|
30439
|
+
}
|
|
30440
|
+
};
|
|
30441
|
+
}
|
|
29613
30442
|
if (ctx.memoryManager && ctx.config.memory?.injection?.enabled !== false) {
|
|
29614
30443
|
hooks["experimental.chat.system.transform"] = createSystemTransformHook(ctx);
|
|
29615
30444
|
}
|