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/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 builder = createMemoryContextBuilder(ctx.memoryManager, {
29565
- budgetTokens: ctx.config.memory?.injection?.budgetTokens ?? 800,
29566
- format: ctx.config.memory?.injection?.format ?? "structured",
29567
- priorityTypes: ctx.config.memory?.injection?.priorityTypes ?? ["decision", "observation", "todo"],
29568
- includeDecisions: true,
29569
- includeRecentActivity: true
29570
- });
29571
- const currentPhase = ctx.stateManager.getState().workflow.phase;
29572
- let memoryContext;
29573
- if (currentPhase) {
29574
- memoryContext = await builder.buildPhaseContext(currentPhase);
29575
- } else {
29576
- memoryContext = await builder.buildRecentContext(10);
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
- const enhancedSystem = `${output.system}
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
- log("Memory context injected", {
30289
+ }
30290
+ log("System context injected", {
29592
30291
  originalLength: output.system.length,
29593
- contextLength: memoryContext.length,
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 memory context", error92);
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
- ...createToolLifecycleHooks(ctx)
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
  }