@gempack/squad-mcp 0.5.0 → 0.6.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.
Files changed (96) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/CHANGELOG.md +260 -17
  4. package/INSTALL.md +156 -24
  5. package/README.md +279 -27
  6. package/agents/{PO.md → product-owner.md} +33 -1
  7. package/agents/{Senior-Architect.md → senior-architect.md} +33 -1
  8. package/agents/{Senior-DBA.md → senior-dba.md} +33 -1
  9. package/agents/{Senior-Dev-Reviewer.md → senior-dev-reviewer.md} +33 -1
  10. package/agents/{Senior-Dev-Security.md → senior-dev-security.md} +33 -1
  11. package/agents/{Senior-Developer.md → senior-developer.md} +33 -1
  12. package/agents/{Senior-QA.md → senior-qa.md} +33 -1
  13. package/agents/{TechLead-Consolidator.md → tech-lead-consolidator.md} +7 -1
  14. package/agents/{TechLead-Planner.md → tech-lead-planner.md} +7 -1
  15. package/commands/squad-review.md +10 -58
  16. package/commands/squad.md +11 -70
  17. package/dist/config/ownership-matrix.d.ts +24 -2
  18. package/dist/config/ownership-matrix.js +466 -139
  19. package/dist/config/ownership-matrix.js.map +1 -1
  20. package/dist/config/squad-yaml.d.ts +242 -0
  21. package/dist/config/squad-yaml.js +403 -0
  22. package/dist/config/squad-yaml.js.map +1 -0
  23. package/dist/errors.d.ts +1 -1
  24. package/dist/errors.js +1 -1
  25. package/dist/errors.js.map +1 -1
  26. package/dist/format/pr-review.d.ts +61 -0
  27. package/dist/format/pr-review.js +146 -0
  28. package/dist/format/pr-review.js.map +1 -0
  29. package/dist/index.js +19 -13
  30. package/dist/index.js.map +1 -1
  31. package/dist/learning/format.d.ts +29 -0
  32. package/dist/learning/format.js +55 -0
  33. package/dist/learning/format.js.map +1 -0
  34. package/dist/learning/store.d.ts +102 -0
  35. package/dist/learning/store.js +169 -0
  36. package/dist/learning/store.js.map +1 -0
  37. package/dist/resources/agent-loader.d.ts +1 -1
  38. package/dist/resources/agent-loader.js +53 -40
  39. package/dist/resources/agent-loader.js.map +1 -1
  40. package/dist/tasks/select.d.ts +64 -0
  41. package/dist/tasks/select.js +84 -0
  42. package/dist/tasks/select.js.map +1 -0
  43. package/dist/tasks/store.d.ts +338 -0
  44. package/dist/tasks/store.js +321 -0
  45. package/dist/tasks/store.js.map +1 -0
  46. package/dist/tools/compose-advisory-bundle.d.ts +5 -5
  47. package/dist/tools/compose-advisory-bundle.js +24 -12
  48. package/dist/tools/compose-advisory-bundle.js.map +1 -1
  49. package/dist/tools/compose-prd-parse.d.ts +53 -0
  50. package/dist/tools/compose-prd-parse.js +167 -0
  51. package/dist/tools/compose-prd-parse.js.map +1 -0
  52. package/dist/tools/compose-squad-workflow.d.ts +28 -10
  53. package/dist/tools/compose-squad-workflow.js +0 -0
  54. package/dist/tools/compose-squad-workflow.js.map +1 -1
  55. package/dist/tools/consolidate.d.ts +55 -4
  56. package/dist/tools/consolidate.js +87 -15
  57. package/dist/tools/consolidate.js.map +1 -1
  58. package/dist/tools/expand-task.d.ts +51 -0
  59. package/dist/tools/expand-task.js +35 -0
  60. package/dist/tools/expand-task.js.map +1 -0
  61. package/dist/tools/list-tasks.d.ts +31 -0
  62. package/dist/tools/list-tasks.js +50 -0
  63. package/dist/tools/list-tasks.js.map +1 -0
  64. package/dist/tools/next-task.d.ts +37 -0
  65. package/dist/tools/next-task.js +60 -0
  66. package/dist/tools/next-task.js.map +1 -0
  67. package/dist/tools/read-learnings.d.ts +53 -0
  68. package/dist/tools/read-learnings.js +72 -0
  69. package/dist/tools/read-learnings.js.map +1 -0
  70. package/dist/tools/read-squad-config.d.ts +23 -0
  71. package/dist/tools/read-squad-config.js +34 -0
  72. package/dist/tools/read-squad-config.js.map +1 -0
  73. package/dist/tools/record-learning.d.ts +62 -0
  74. package/dist/tools/record-learning.js +80 -0
  75. package/dist/tools/record-learning.js.map +1 -0
  76. package/dist/tools/record-tasks.d.ts +71 -0
  77. package/dist/tools/record-tasks.js +45 -0
  78. package/dist/tools/record-tasks.js.map +1 -0
  79. package/dist/tools/registry.d.ts +1 -1
  80. package/dist/tools/registry.js +71 -39
  81. package/dist/tools/registry.js.map +1 -1
  82. package/dist/tools/score-rubric.d.ts +74 -0
  83. package/dist/tools/score-rubric.js +140 -0
  84. package/dist/tools/score-rubric.js.map +1 -0
  85. package/dist/tools/slice-files-for-task.d.ts +31 -0
  86. package/dist/tools/slice-files-for-task.js +52 -0
  87. package/dist/tools/slice-files-for-task.js.map +1 -0
  88. package/dist/tools/update-task-status.d.ts +29 -0
  89. package/dist/tools/update-task-status.js +35 -0
  90. package/dist/tools/update-task-status.js.map +1 -0
  91. package/package.json +4 -1
  92. package/skills/squad/SKILL.md +454 -0
  93. package/tools/post-review.mjs +212 -0
  94. /package/agents/{Skill-Squad-Dev.md → _shared/Skill-Squad-Dev.md} +0 -0
  95. /package/agents/{Skill-Squad-Review.md → _shared/Skill-Squad-Review.md} +0 -0
  96. /package/agents/{_Severity-and-Ownership.md → _shared/_Severity-and-Ownership.md} +0 -0
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import { registerTools, dispatchTool, listTools } from './tools/registry.js';
6
- import { listResources, readResource } from './resources/registry.js';
7
- import { listPrompts, getPrompt } from './prompts/registry.js';
8
- import { logger, setupProcessHandlers } from './observability/logger.js';
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { registerTools, dispatchTool, listTools } from "./tools/registry.js";
6
+ import { listResources, readResource } from "./resources/registry.js";
7
+ import { listPrompts, getPrompt } from "./prompts/registry.js";
8
+ import { logger, setupProcessHandlers } from "./observability/logger.js";
9
9
  setupProcessHandlers();
10
- const SERVER_VERSION = '0.5.0';
10
+ const SERVER_VERSION = "0.6.0";
11
11
  const server = new Server({
12
- name: 'squad-mcp',
12
+ name: "squad-mcp",
13
13
  version: SERVER_VERSION,
14
14
  }, {
15
15
  capabilities: {
@@ -19,15 +19,21 @@ const server = new Server({
19
19
  },
20
20
  });
21
21
  registerTools();
22
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: listTools() }));
22
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
23
+ tools: listTools(),
24
+ }));
23
25
  server.setRequestHandler(CallToolRequestSchema, async (req) => dispatchTool(req.params.name, req.params.arguments ?? {}));
24
- server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: await listResources() }));
26
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
27
+ resources: await listResources(),
28
+ }));
25
29
  server.setRequestHandler(ReadResourceRequestSchema, async (req) => readResource(req.params.uri));
26
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: listPrompts() }));
30
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
31
+ prompts: listPrompts(),
32
+ }));
27
33
  server.setRequestHandler(GetPromptRequestSchema, async (req) => getPrompt(req.params.name, req.params.arguments ?? {}));
28
34
  const transport = new StdioServerTransport();
29
35
  await server.connect(transport);
30
- logger.info('server started', {
36
+ logger.info("server started", {
31
37
  details: { version: SERVER_VERSION, tools: listTools().length },
32
38
  });
33
39
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEzE,oBAAoB,EAAE,CAAC;AAEvB,MAAM,cAAc,GAAG,OAAO,CAAC;AAE/B,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;IACE,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,cAAc;CACxB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;QACT,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;KACZ;CACF,CACF,CAAC;AAEF,aAAa,EAAE,CAAC;AAEhB,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;AACvF,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;AAE1H,MAAM,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC;AACzG,MAAM,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAEjG,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;AAC7F,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;AAExH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAEhC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE;IAC5B,OAAO,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE;CAChE,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEzE,oBAAoB,EAAE,CAAC;AAEvB,MAAM,cAAc,GAAG,OAAO,CAAC;AAE/B,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;IACE,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,cAAc;CACxB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;QACT,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;KACZ;CACF,CACF,CAAC;AAEF,aAAa,EAAE,CAAC;AAEhB,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5D,KAAK,EAAE,SAAS,EAAE;CACnB,CAAC,CAAC,CAAC;AACJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAC5D,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAC1D,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAChE,SAAS,EAAE,MAAM,aAAa,EAAE;CACjC,CAAC,CAAC,CAAC;AACJ,MAAM,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAChE,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAC7B,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAC9D,OAAO,EAAE,WAAW,EAAE;CACvB,CAAC,CAAC,CAAC;AACJ,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAC7D,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CACvD,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAEhC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE;IAC5B,OAAO,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE;CAChE,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { LearningEntry } from "./store.js";
2
+ export interface FormatLearningsOptions {
3
+ /**
4
+ * Filter to learnings whose `scope` glob matches at least one of these
5
+ * paths. Used to narrow per-PR injection to learnings relevant to the
6
+ * files actually changed. When empty / undefined, all entries pass.
7
+ */
8
+ changedFiles?: string[];
9
+ /**
10
+ * Cap the rendered list. Default 50. Tail-most-recent. Hard cap of 200
11
+ * to prevent prompt bloat.
12
+ */
13
+ limit?: number;
14
+ /**
15
+ * Heading level for the section. Default `## Past team decisions`. Adjust
16
+ * when injecting into a prompt that already nests under a higher heading.
17
+ */
18
+ heading?: string;
19
+ }
20
+ /**
21
+ * Render a slice of learning entries as a markdown block ready to inject into
22
+ * an agent or consolidator prompt. Pure function. The block is intentionally
23
+ * compact — each entry is one numbered line so the LLM can scan quickly and
24
+ * reference back ("see decision #4 — already declined this CSRF flag").
25
+ *
26
+ * Returns `''` when no entries qualify after filtering. Callers should check
27
+ * the empty case before injecting (avoid empty headers in prompts).
28
+ */
29
+ export declare function formatLearningsForPrompt(entries: LearningEntry[], options?: FormatLearningsOptions): string;
@@ -0,0 +1,55 @@
1
+ import { matchesGlob } from "../config/squad-yaml.js";
2
+ const HARD_LIMIT = 200;
3
+ const DEFAULT_LIMIT = 50;
4
+ const DEFAULT_HEADING = "## Past team decisions";
5
+ /**
6
+ * Render a slice of learning entries as a markdown block ready to inject into
7
+ * an agent or consolidator prompt. Pure function. The block is intentionally
8
+ * compact — each entry is one numbered line so the LLM can scan quickly and
9
+ * reference back ("see decision #4 — already declined this CSRF flag").
10
+ *
11
+ * Returns `''` when no entries qualify after filtering. Callers should check
12
+ * the empty case before injecting (avoid empty headers in prompts).
13
+ */
14
+ export function formatLearningsForPrompt(entries, options = {}) {
15
+ const limit = Math.min(options.limit ?? DEFAULT_LIMIT, HARD_LIMIT);
16
+ const heading = options.heading ?? DEFAULT_HEADING;
17
+ // Filter by scope glob match against changedFiles. An entry without a scope
18
+ // is repo-wide and always passes. An entry WITH a scope passes if any
19
+ // changed file matches the scope glob.
20
+ let relevant = entries;
21
+ if (options.changedFiles && options.changedFiles.length > 0) {
22
+ relevant = entries.filter((e) => {
23
+ if (!e.scope)
24
+ return true;
25
+ return options.changedFiles.some((p) => matchesGlob(e.scope, p));
26
+ });
27
+ }
28
+ // Tail-N most recent. Append order = chronological, so slice from end.
29
+ const tail = relevant.slice(-limit);
30
+ if (tail.length === 0)
31
+ return "";
32
+ // Render most-recent-first to bias the LLM's attention.
33
+ const ordered = [...tail].reverse();
34
+ const lines = [];
35
+ lines.push(heading);
36
+ lines.push("");
37
+ lines.push(`Recent decisions on similar findings (${ordered.length} of ${entries.length} total, most recent first). When evaluating a new finding that matches a past **rejected** decision (similar agent + similar finding text + matching scope), suppress or downgrade severity. When a finding contradicts a past **accepted** decision, flag the contradiction explicitly.`);
38
+ lines.push("");
39
+ ordered.forEach((e, i) => {
40
+ const ref = e.pr
41
+ ? `PR #${e.pr}`
42
+ : e.branch
43
+ ? `branch ${e.branch}`
44
+ : e.ts.slice(0, 10);
45
+ const verdict = e.decision === "reject" ? "REJECTED" : "ACCEPTED";
46
+ const sev = e.severity ? ` [${e.severity}]` : "";
47
+ const scope = e.scope ? ` (scope: \`${e.scope}\`)` : "";
48
+ lines.push(`${i + 1}. **${verdict}** at ${ref}${scope} — ${e.agent}${sev}: "${e.finding}"`);
49
+ if (e.reason) {
50
+ lines.push(` Reason: ${e.reason}`);
51
+ }
52
+ });
53
+ return lines.join("\n") + "\n";
54
+ }
55
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/learning/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import { z } from "zod";
2
+ import { type AgentName } from "../config/ownership-matrix.js";
3
+ /**
4
+ * One row in `.squad/learnings.jsonl`. Append-only — entries are never
5
+ * rewritten, just superseded by later ones with the same (agent, finding,
6
+ * scope) tuple. Keep the schema small; rich query semantics are out of
7
+ * scope for V1 (the consolidator does free-text recall, not vector search).
8
+ */
9
+ declare const learningEntrySchema: z.ZodObject<{
10
+ /** ISO 8601 timestamp. Required for ordering. */
11
+ ts: z.ZodString;
12
+ /** PR number when recorded from `/squad-review #N`; optional otherwise. */
13
+ pr: z.ZodOptional<z.ZodNumber>;
14
+ /** Branch name when recorded from a local review (no PR ref). */
15
+ branch: z.ZodOptional<z.ZodString>;
16
+ /** Which agent's finding this decision concerns. */
17
+ agent: z.ZodEnum<[AgentName, ...AgentName[]]>;
18
+ /** Severity at the time of the decision (Blocker / Major / Minor / Suggestion). */
19
+ severity: z.ZodOptional<z.ZodEnum<["Blocker", "Major", "Minor", "Suggestion"]>>;
20
+ /** Short title of the finding — same shape as Finding.title in consolidate. */
21
+ finding: z.ZodString;
22
+ /** Whether the team accepted or rejected this finding. */
23
+ decision: z.ZodEnum<["accept", "reject"]>;
24
+ /** Free-form rationale. Surfaces in the consolidator prompt. */
25
+ reason: z.ZodOptional<z.ZodString>;
26
+ /**
27
+ * Glob-ish path scope this decision applies to (e.g. "src/auth/**"). When
28
+ * absent, the decision applies repo-wide. Used by the formatter to filter
29
+ * learnings down to those relevant to the current diff.
30
+ */
31
+ scope: z.ZodOptional<z.ZodString>;
32
+ }, "strip", z.ZodTypeAny, {
33
+ agent: AgentName;
34
+ ts: string;
35
+ finding: string;
36
+ decision: "accept" | "reject";
37
+ reason?: string | undefined;
38
+ severity?: "Blocker" | "Major" | "Minor" | "Suggestion" | undefined;
39
+ pr?: number | undefined;
40
+ branch?: string | undefined;
41
+ scope?: string | undefined;
42
+ }, {
43
+ agent: AgentName;
44
+ ts: string;
45
+ finding: string;
46
+ decision: "accept" | "reject";
47
+ reason?: string | undefined;
48
+ severity?: "Blocker" | "Major" | "Minor" | "Suggestion" | undefined;
49
+ pr?: number | undefined;
50
+ branch?: string | undefined;
51
+ scope?: string | undefined;
52
+ }>;
53
+ export type LearningEntry = z.infer<typeof learningEntrySchema>;
54
+ /**
55
+ * Default location for the JSONL file, relative to workspace_root. Repo-versioned
56
+ * by convention; the team commits `.squad/learnings.jsonl` along with the code so
57
+ * decisions are auditable in PR diffs.
58
+ */
59
+ export declare const DEFAULT_LEARNING_PATH = ".squad/learnings.jsonl";
60
+ /** Test-only: clear the per-process cache. Production code MUST NOT call this. */
61
+ export declare function __resetLearningStoreCacheForTests(): void;
62
+ /**
63
+ * Read all learnings from the JSONL file. Returns [] if the file does not exist
64
+ * (a fresh repo with no decisions recorded is the common case). Throws on
65
+ * parse failure of any individual line — callers may want to soft-fail, but
66
+ * silent corruption is worse than loud rejection.
67
+ */
68
+ export declare function readLearnings(workspaceRoot: string, options?: {
69
+ configuredPath?: string;
70
+ }): Promise<LearningEntry[]>;
71
+ /**
72
+ * Append a new learning entry to the JSONL file. Creates the directory and
73
+ * file if needed. Atomic at the append level (single fs.appendFile call —
74
+ * Node serialises this on POSIX); concurrent appenders may interleave entries
75
+ * but never corrupt them line-wise.
76
+ *
77
+ * Stamps the timestamp here if the caller did not supply one — gives a single
78
+ * source of clock truth and prevents stale ts in CLI invocations.
79
+ */
80
+ export declare function appendLearning(workspaceRoot: string, entry: Omit<LearningEntry, "ts"> & {
81
+ ts?: string;
82
+ }, options?: {
83
+ configuredPath?: string;
84
+ }): Promise<{
85
+ filePath: string;
86
+ entry: LearningEntry;
87
+ }>;
88
+ /**
89
+ * Filter helper: pick the most recent N entries, optionally narrowed by
90
+ * agent. Used by the formatter to inject a small, relevant slice into the
91
+ * agent / consolidator prompt.
92
+ *
93
+ * Sort: input is in append order (oldest first). We slice the tail. The
94
+ * filter happens BEFORE the slice — `recentForAgent('senior-dba', 50)`
95
+ * returns the last 50 DBA decisions, not the last 50 decisions overall
96
+ * filtered to DBA (which would often return zero on diverse repos).
97
+ */
98
+ export declare function tailRecent(entries: LearningEntry[], limit: number, options?: {
99
+ agent?: AgentName;
100
+ decision?: "accept" | "reject";
101
+ }): LearningEntry[];
102
+ export {};
@@ -0,0 +1,169 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ import { AGENT_NAMES_TUPLE, } from "../config/ownership-matrix.js";
5
+ import { SquadError } from "../errors.js";
6
+ import { logger } from "../observability/logger.js";
7
+ /**
8
+ * One row in `.squad/learnings.jsonl`. Append-only — entries are never
9
+ * rewritten, just superseded by later ones with the same (agent, finding,
10
+ * scope) tuple. Keep the schema small; rich query semantics are out of
11
+ * scope for V1 (the consolidator does free-text recall, not vector search).
12
+ */
13
+ const learningEntrySchema = z.object({
14
+ /** ISO 8601 timestamp. Required for ordering. */
15
+ ts: z.string().min(1).max(40),
16
+ /** PR number when recorded from `/squad-review #N`; optional otherwise. */
17
+ pr: z.number().int().positive().optional(),
18
+ /** Branch name when recorded from a local review (no PR ref). */
19
+ branch: z.string().min(1).max(255).optional(),
20
+ /** Which agent's finding this decision concerns. */
21
+ agent: z.enum(AGENT_NAMES_TUPLE),
22
+ /** Severity at the time of the decision (Blocker / Major / Minor / Suggestion). */
23
+ severity: z.enum(["Blocker", "Major", "Minor", "Suggestion"]).optional(),
24
+ /** Short title of the finding — same shape as Finding.title in consolidate. */
25
+ finding: z.string().min(1).max(2048),
26
+ /** Whether the team accepted or rejected this finding. */
27
+ decision: z.enum(["accept", "reject"]),
28
+ /** Free-form rationale. Surfaces in the consolidator prompt. */
29
+ reason: z.string().max(4096).optional(),
30
+ /**
31
+ * Glob-ish path scope this decision applies to (e.g. "src/auth/**"). When
32
+ * absent, the decision applies repo-wide. Used by the formatter to filter
33
+ * learnings down to those relevant to the current diff.
34
+ */
35
+ scope: z.string().min(1).max(512).optional(),
36
+ });
37
+ /**
38
+ * Default location for the JSONL file, relative to workspace_root. Repo-versioned
39
+ * by convention; the team commits `.squad/learnings.jsonl` along with the code so
40
+ * decisions are auditable in PR diffs.
41
+ */
42
+ export const DEFAULT_LEARNING_PATH = ".squad/learnings.jsonl";
43
+ const cache = new Map();
44
+ /** Test-only: clear the per-process cache. Production code MUST NOT call this. */
45
+ export function __resetLearningStoreCacheForTests() {
46
+ cache.clear();
47
+ }
48
+ function resolveLearningFile(workspaceRoot, configuredPath) {
49
+ const rel = configuredPath ?? DEFAULT_LEARNING_PATH;
50
+ return path.resolve(workspaceRoot, rel);
51
+ }
52
+ /**
53
+ * Read all learnings from the JSONL file. Returns [] if the file does not exist
54
+ * (a fresh repo with no decisions recorded is the common case). Throws on
55
+ * parse failure of any individual line — callers may want to soft-fail, but
56
+ * silent corruption is worse than loud rejection.
57
+ */
58
+ export async function readLearnings(workspaceRoot, options = {}) {
59
+ const filePath = resolveLearningFile(workspaceRoot, options.configuredPath);
60
+ const absRoot = path.resolve(workspaceRoot);
61
+ let stat;
62
+ try {
63
+ stat = await fs.stat(filePath);
64
+ }
65
+ catch {
66
+ // No file — first run, no learnings yet.
67
+ return [];
68
+ }
69
+ if (!stat.isFile()) {
70
+ return [];
71
+ }
72
+ const cached = cache.get(absRoot);
73
+ if (cached &&
74
+ cached.filePath === filePath &&
75
+ cached.mtimeMs === stat.mtimeMs) {
76
+ return cached.entries;
77
+ }
78
+ let raw;
79
+ try {
80
+ raw = await fs.readFile(filePath, "utf8");
81
+ }
82
+ catch (err) {
83
+ throw new SquadError("CONFIG_READ_FAILED", `failed to read learnings file ${filePath}: ${err.message}`, { source: filePath });
84
+ }
85
+ const lines = raw.split(/\r?\n/);
86
+ const entries = [];
87
+ let lineNo = 0;
88
+ for (const line of lines) {
89
+ lineNo++;
90
+ const trimmed = line.trim();
91
+ if (trimmed === "")
92
+ continue; // skip blank lines (trailing newline, spacing)
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(trimmed);
96
+ }
97
+ catch (err) {
98
+ throw new SquadError("INVALID_INPUT", `${filePath}:${lineNo}: invalid JSON: ${err.message}`, { source: filePath, line: lineNo });
99
+ }
100
+ const validated = learningEntrySchema.safeParse(parsed);
101
+ if (!validated.success) {
102
+ throw new SquadError("INVALID_INPUT", `${filePath}:${lineNo}: schema violation: ${validated.error.message}`, {
103
+ source: filePath,
104
+ line: lineNo,
105
+ issues: validated.error.issues.length,
106
+ });
107
+ }
108
+ entries.push(validated.data);
109
+ }
110
+ cache.set(absRoot, { mtimeMs: stat.mtimeMs, filePath, entries });
111
+ return entries;
112
+ }
113
+ /**
114
+ * Append a new learning entry to the JSONL file. Creates the directory and
115
+ * file if needed. Atomic at the append level (single fs.appendFile call —
116
+ * Node serialises this on POSIX); concurrent appenders may interleave entries
117
+ * but never corrupt them line-wise.
118
+ *
119
+ * Stamps the timestamp here if the caller did not supply one — gives a single
120
+ * source of clock truth and prevents stale ts in CLI invocations.
121
+ */
122
+ export async function appendLearning(workspaceRoot, entry, options = {}) {
123
+ const ts = entry.ts ?? new Date().toISOString();
124
+ const candidate = { ...entry, ts };
125
+ const validated = learningEntrySchema.safeParse(candidate);
126
+ if (!validated.success) {
127
+ throw new SquadError("INVALID_INPUT", `learning entry schema violation: ${validated.error.message}`, { issues: validated.error.issues.length });
128
+ }
129
+ const filePath = resolveLearningFile(workspaceRoot, options.configuredPath);
130
+ const dir = path.dirname(filePath);
131
+ await fs.mkdir(dir, { recursive: true });
132
+ // One JSON object per line, no pretty-print — keeps the file grep-friendly
133
+ // and minimises diff churn when entries get re-ordered (which they don't,
134
+ // but defensive).
135
+ const line = JSON.stringify(validated.data) + "\n";
136
+ await fs.appendFile(filePath, line, "utf8");
137
+ // Invalidate cache so next readLearnings reflects the append.
138
+ const absRoot = path.resolve(workspaceRoot);
139
+ cache.delete(absRoot);
140
+ logger.info("learning appended", {
141
+ details: {
142
+ file: filePath,
143
+ agent: validated.data.agent,
144
+ decision: validated.data.decision,
145
+ },
146
+ });
147
+ return { filePath, entry: validated.data };
148
+ }
149
+ /**
150
+ * Filter helper: pick the most recent N entries, optionally narrowed by
151
+ * agent. Used by the formatter to inject a small, relevant slice into the
152
+ * agent / consolidator prompt.
153
+ *
154
+ * Sort: input is in append order (oldest first). We slice the tail. The
155
+ * filter happens BEFORE the slice — `recentForAgent('senior-dba', 50)`
156
+ * returns the last 50 DBA decisions, not the last 50 decisions overall
157
+ * filtered to DBA (which would often return zero on diverse repos).
158
+ */
159
+ export function tailRecent(entries, limit, options = {}) {
160
+ let filtered = entries;
161
+ if (options.agent) {
162
+ filtered = filtered.filter((e) => e.agent === options.agent);
163
+ }
164
+ if (options.decision) {
165
+ filtered = filtered.filter((e) => e.decision === options.decision);
166
+ }
167
+ return filtered.slice(-limit);
168
+ }
169
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/learning/store.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { type AgentName } from '../config/ownership-matrix.js';
1
+ import { type AgentName } from "../config/ownership-matrix.js";
2
2
  export declare const SHARED_FILES: string[];
3
3
  /**
4
4
  * Returns the configured override directory and whether it was set explicitly
@@ -1,31 +1,35 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import { fileURLToPath } from 'node:url';
5
- import { AGENTS } from '../config/ownership-matrix.js';
6
- import { SquadError } from '../errors.js';
7
- import { logger } from '../observability/logger.js';
8
- import { validateOverrideDir, validateOverrideFile, rejectionToError, getAllowlistSize, __resetOverrideAllowlistCache, } from '../util/override-allowlist.js';
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { fileURLToPath } from "node:url";
5
+ import { AGENTS } from "../config/ownership-matrix.js";
6
+ import { SquadError } from "../errors.js";
7
+ import { logger } from "../observability/logger.js";
8
+ import { validateOverrideDir, validateOverrideFile, rejectionToError, getAllowlistSize, __resetOverrideAllowlistCache, } from "../util/override-allowlist.js";
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  const AGENT_FILE_MAP = {
11
- po: 'PO.md',
12
- 'tech-lead-planner': 'TechLead-Planner.md',
13
- 'tech-lead-consolidator': 'TechLead-Consolidator.md',
14
- 'senior-architect': 'Senior-Architect.md',
15
- 'senior-dba': 'Senior-DBA.md',
16
- 'senior-developer': 'Senior-Developer.md',
17
- 'senior-dev-reviewer': 'Senior-Dev-Reviewer.md',
18
- 'senior-dev-security': 'Senior-Dev-Security.md',
19
- 'senior-qa': 'Senior-QA.md',
11
+ 'product-owner': "product-owner.md",
12
+ "tech-lead-planner": "tech-lead-planner.md",
13
+ "tech-lead-consolidator": "tech-lead-consolidator.md",
14
+ "senior-architect": "senior-architect.md",
15
+ "senior-dba": "senior-dba.md",
16
+ "senior-developer": "senior-developer.md",
17
+ "senior-dev-reviewer": "senior-dev-reviewer.md",
18
+ "senior-dev-security": "senior-dev-security.md",
19
+ "senior-qa": "senior-qa.md",
20
20
  };
21
- export const SHARED_FILES = ['_Severity-and-Ownership.md', 'Skill-Squad-Dev.md', 'Skill-Squad-Review.md'];
21
+ export const SHARED_FILES = [
22
+ "_shared/_Severity-and-Ownership.md",
23
+ "_shared/Skill-Squad-Dev.md",
24
+ "_shared/Skill-Squad-Review.md",
25
+ ];
22
26
  function defaultLocalDir() {
23
- if (process.platform === 'win32') {
24
- const appdata = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming');
25
- return path.join(appdata, 'squad-mcp', 'agents');
27
+ if (process.platform === "win32") {
28
+ const appdata = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
29
+ return path.join(appdata, "squad-mcp", "agents");
26
30
  }
27
- const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
28
- return path.join(xdg, 'squad-mcp', 'agents');
31
+ const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
32
+ return path.join(xdg, "squad-mcp", "agents");
29
33
  }
30
34
  /**
31
35
  * Returns the configured override directory and whether it was set explicitly
@@ -33,13 +37,13 @@ function defaultLocalDir() {
33
37
  */
34
38
  export function getLocalDir() {
35
39
  const env = process.env.SQUAD_AGENTS_DIR;
36
- if (env !== undefined && env !== '') {
40
+ if (env !== undefined && env !== "") {
37
41
  return { rawDir: env, explicit: true };
38
42
  }
39
43
  return { rawDir: defaultLocalDir(), explicit: false };
40
44
  }
41
45
  export function getEmbeddedDir() {
42
- return path.resolve(__dirname, '..', '..', 'agents');
46
+ return path.resolve(__dirname, "..", "..", "agents");
43
47
  }
44
48
  async function exists(p) {
45
49
  try {
@@ -86,7 +90,7 @@ export function __resetAgentLoaderForTests() {
86
90
  */
87
91
  async function createSecureDir(dir) {
88
92
  await fs.mkdir(dir, { recursive: true, mode: 0o700 });
89
- if (process.platform !== 'win32') {
93
+ if (process.platform !== "win32") {
90
94
  await fs.chmod(dir, 0o700);
91
95
  }
92
96
  }
@@ -98,7 +102,7 @@ async function createSecureDir(dir) {
98
102
  */
99
103
  async function copyFileSecure(src, dst) {
100
104
  await fs.copyFile(src, dst);
101
- if (process.platform !== 'win32') {
105
+ if (process.platform !== "win32") {
102
106
  await fs.chmod(dst, 0o600);
103
107
  }
104
108
  }
@@ -111,7 +115,7 @@ async function copyFileSecure(src, dst) {
111
115
  * not produce duplicate warnings.
112
116
  */
113
117
  async function checkOverrideDirPerms(dir) {
114
- if (process.platform === 'win32')
118
+ if (process.platform === "win32")
115
119
  return;
116
120
  if (permWarnEmitted)
117
121
  return;
@@ -119,10 +123,10 @@ async function checkOverrideDirPerms(dir) {
119
123
  try {
120
124
  const s = await fs.stat(dir);
121
125
  if ((s.mode & 0o002) !== 0) {
122
- logger.warn('override directory is world-writable', {
126
+ logger.warn("override directory is world-writable", {
123
127
  details: {
124
128
  configured_path: dir,
125
- mode: '0o' + (s.mode & 0o777).toString(8),
129
+ mode: "0o" + (s.mode & 0o777).toString(8),
126
130
  recommendation: `chmod 700 ${dir}`,
127
131
  },
128
132
  });
@@ -137,7 +141,7 @@ async function ensureEmbeddedDir() {
137
141
  return;
138
142
  const dir = getEmbeddedDir();
139
143
  if (!(await exists(dir))) {
140
- throw new SquadError('AGENT_DIR_MISSING', `embedded agents directory missing at ${dir}`);
144
+ throw new SquadError("AGENT_DIR_MISSING", `embedded agents directory missing at ${dir}`);
141
145
  }
142
146
  embeddedAsserted = true;
143
147
  }
@@ -164,7 +168,7 @@ async function resolveOverride() {
164
168
  if (!(await isDirectory(rawDir))) {
165
169
  if (explicit && !overrideMissingWarnEmitted) {
166
170
  overrideMissingWarnEmitted = true;
167
- logger.warn('SQUAD_AGENTS_DIR set but directory not found; falling back to embedded defaults', {
171
+ logger.warn("SQUAD_AGENTS_DIR set but directory not found; falling back to embedded defaults", {
168
172
  details: { configured_path: rawDir },
169
173
  });
170
174
  }
@@ -176,14 +180,14 @@ async function resolveOverride() {
176
180
  if (explicit) {
177
181
  const size = await getAllowlistSize();
178
182
  const err = rejectionToError(result, size);
179
- logger.warn('SQUAD_AGENTS_DIR rejected', {
183
+ logger.warn("SQUAD_AGENTS_DIR rejected", {
180
184
  error_code: err.code,
181
185
  details: { reason: result.reason, configured_path: rawDir },
182
186
  });
183
187
  throw err;
184
188
  }
185
189
  // Platform-default rejection: log warn, fall back silently. Rare.
186
- logger.warn('platform default agent directory failed validation', {
190
+ logger.warn("platform default agent directory failed validation", {
187
191
  details: { reason: result.reason, configured_path: rawDir },
188
192
  });
189
193
  overrideValidationCache.set(rawDir, null);
@@ -195,13 +199,15 @@ async function resolveOverride() {
195
199
  resolved_path: result.resolvedPath,
196
200
  allowlist_match: result.allowlistMatch,
197
201
  has_unsafe_override: result.unsafeOverride,
198
- source: explicit ? 'env' : 'platform_default',
202
+ source: explicit ? "env" : "platform_default",
199
203
  };
200
204
  if (result.unsafeOverride) {
201
- logger.warn('agent override active (unsafe escape hatch)', { details: fields });
205
+ logger.warn("agent override active (unsafe escape hatch)", {
206
+ details: fields,
207
+ });
202
208
  }
203
209
  else {
204
- logger.info('agent override active', { details: fields });
210
+ logger.info("agent override active", { details: fields });
205
211
  }
206
212
  }
207
213
  await checkOverrideDirPerms(result.resolvedPath);
@@ -214,7 +220,7 @@ async function resolveOverride() {
214
220
  */
215
221
  function assertKnownAgent(name) {
216
222
  if (!Object.prototype.hasOwnProperty.call(AGENT_FILE_MAP, name)) {
217
- throw new SquadError('UNKNOWN_AGENT', `unknown agent: ${name}`, { name });
223
+ throw new SquadError("UNKNOWN_AGENT", `unknown agent: ${name}`, { name });
218
224
  }
219
225
  }
220
226
  export async function resolveAgentFile(name) {
@@ -233,7 +239,9 @@ export async function resolveAgentFile(name) {
233
239
  export async function resolveSharedFile(file) {
234
240
  await ensureEmbeddedDir();
235
241
  if (!SHARED_FILES.includes(file)) {
236
- throw new SquadError('INVALID_INPUT', `shared file not allowed: ${file}`, { file });
242
+ throw new SquadError("INVALID_INPUT", `shared file not allowed: ${file}`, {
243
+ file,
244
+ });
237
245
  }
238
246
  const override = await resolveOverride();
239
247
  if (override) {
@@ -245,7 +253,7 @@ export async function resolveSharedFile(file) {
245
253
  }
246
254
  export async function readAgentDefinition(name) {
247
255
  const filePath = await resolveAgentFile(name);
248
- return fs.readFile(filePath, 'utf8');
256
+ return fs.readFile(filePath, "utf8");
249
257
  }
250
258
  export async function listAvailableAgents() {
251
259
  // Trigger validation so misconfigured overrides surface here too.
@@ -282,6 +290,11 @@ export async function initLocalConfig(force = false) {
282
290
  skipped.push(file);
283
291
  continue;
284
292
  }
293
+ // Shared docs live under _shared/; ensure the parent dir exists before copyFile.
294
+ const parent = path.dirname(dst);
295
+ if (parent !== rawDir) {
296
+ await createSecureDir(parent);
297
+ }
285
298
  const src = path.join(getEmbeddedDir(), file);
286
299
  await copyFileSecure(src, dst);
287
300
  created.push(file);