@esneiderbravo/speclaw 2.0.12 → 2.0.14

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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/init.js +3 -2
  3. package/dist/cli/commands/lawbook.js +4 -1
  4. package/dist/cli/commands/query.js +2 -2
  5. package/dist/cli/commands/ship-on-stop.js +46 -0
  6. package/dist/cli/commands/ship.js +44 -0
  7. package/dist/cli/commands/update.js +25 -0
  8. package/dist/cli/index.js +6 -1
  9. package/dist/cli/lib/help.js +36 -4
  10. package/dist/modules/compass/db.js +47 -9
  11. package/dist/modules/compass/explore-rich.js +50 -9
  12. package/dist/modules/compass/extract.js +220 -0
  13. package/dist/modules/compass/find-output.js +172 -0
  14. package/dist/modules/compass/hybrid.js +149 -21
  15. package/dist/modules/compass/indexer.js +26 -2
  16. package/dist/modules/compass/query.js +48 -12
  17. package/dist/modules/compass/register.js +13 -7
  18. package/dist/modules/cortex/harness.js +17 -4
  19. package/dist/modules/cortex/status.js +1 -1
  20. package/dist/modules/foundation/assets/AGENTS.template.md +12 -11
  21. package/dist/modules/foundation/assets/CLAUDE.template.md +16 -12
  22. package/dist/modules/foundation/assets/docs/compass.template.md +11 -4
  23. package/dist/modules/foundation/deps.js +5 -3
  24. package/dist/modules/foundation/dialects/copilot.js +3 -5
  25. package/dist/modules/foundation/graph.js +5 -5
  26. package/dist/modules/foundation/hooks.js +30 -4
  27. package/dist/modules/foundation/laws.js +5 -0
  28. package/dist/modules/foundation/scaffold.js +3 -16
  29. package/dist/modules/lawbook/anchors.js +38 -1
  30. package/dist/modules/lawbook/assets/commands/cortex.md +1 -1
  31. package/dist/modules/lawbook/assets/rules/spec-reports-disciplines.md +6 -0
  32. package/dist/modules/lawbook/assets/rules/spec-tasks-mandatory-steps.md +6 -0
  33. package/dist/modules/lawbook/assets/skills/cortex/SKILL.md +17 -5
  34. package/dist/modules/lawbook/bugfix.js +12 -4
  35. package/dist/modules/lawbook/change-tool.js +7 -0
  36. package/dist/modules/lawbook/engine.js +3 -0
  37. package/dist/modules/lawbook/levels.js +35 -4
  38. package/dist/modules/lawbook/quick.js +17 -6
  39. package/dist/modules/lawbook/ship.js +325 -0
  40. package/dist/shared/agents.js +7 -1
  41. package/dist/shared/mcp.js +6 -4
  42. package/dist/shared/output-budget.js +36 -12
  43. package/package.json +1 -1
  44. package/dist/modules/foundation/assets/workflows/speclaw.yml +0 -26
@@ -37,7 +37,7 @@ export const AGENTS = [
37
37
  id: "agents",
38
38
  label: "Generic (AGENTS.md)",
39
39
  ideDir: ".agents",
40
- linkTargets: ["skills", "agents"],
40
+ linkTargets: ["skills", "commands", "agents", "rules"],
41
41
  },
42
42
  ];
43
43
  /**
@@ -141,6 +141,12 @@ export function configureAgent(projectPath, agentId, report) {
141
141
  if (!agent)
142
142
  throw new Error(`Unknown agent "${agentId}". Known: ${AGENTS.map((a) => a.id).join(", ")}`);
143
143
  const ideRoot = path.join(projectPath, agent.ideDir);
144
+ // An agent folder speclaw creates holds only links into the gitignored
145
+ // ai-specs/ and per-developer wiring: keep it out of version control. A
146
+ // folder that already existed is the user's and is left as it is.
147
+ if (!fs.existsSync(ideRoot)) {
148
+ ensureGitignore(projectPath, `${agent.ideDir}/`, `speclaw ${agent.label} links (regenerated by init; never commit)`, report);
149
+ }
144
150
  for (const target of agent.linkTargets) {
145
151
  const sourceDir = path.join(projectPath, "ai-specs", target);
146
152
  if (!fs.existsSync(sourceDir))
@@ -3,15 +3,17 @@ import { toolDefinitionTokens } from "./schema-tokens.js";
3
3
  import { countWords } from "./tokens.js";
4
4
  import { applyTextBudget } from "./output-budget.js";
5
5
  /**
6
- * Wrap a value as an MCP text tool-result, optionally applying an output budget.
6
+ * Wrap a value as an MCP text tool-result, applying an output budget.
7
7
  *
8
8
  * @param value - Payload to return; strings are emitted verbatim, other values are pretty-printed as JSON.
9
- * @param mode - Output mode (`brief` default) for token budgeting.
9
+ * @param budget - Output mode (`brief` default, unchanged for callers that pass
10
+ * none) or an explicit `{ maxTokens }` cap a handler opted into.
10
11
  * @returns An MCP result object with a single text content block.
11
12
  */
12
- export function text(value, mode = "brief") {
13
+ // Covers: req~find-response-budget~1
14
+ export function text(value, budget = "brief") {
13
15
  const raw = typeof value === "string" ? value : JSON.stringify(value, null, 2);
14
- const budgeted = applyTextBudget(raw, mode);
16
+ const budgeted = applyTextBudget(raw, budget);
15
17
  return {
16
18
  content: [
17
19
  {
@@ -3,26 +3,43 @@ export const OUTPUT_BUDGET = {
3
3
  brief: 1500,
4
4
  full: 4500,
5
5
  };
6
+ /** Brief-mode suffix: the caller can still widen the response with `mode:"full"`. */
6
7
  const TRUNCATION_SUFFIX = `\n… [truncated — use mode:"full" or narrow includes]`;
8
+ /** Suffix under the full ceiling or an explicit cap: widening the mode would not help. */
9
+ const FINAL_TRUNCATION_SUFFIX = `\n… [truncated — narrow includes or the query]`;
7
10
  /** Stable offline token estimate for budgeting (not a real tokenizer). */
8
11
  export function estimateTokens(text) {
9
12
  return Math.ceil(text.length / 4);
10
13
  }
11
14
  /**
12
- * Trim `text` to fit `mode` budget; returns the possibly shortened text and
13
- * whether truncation occurred.
15
+ * Token ceiling of a {@link TextBudget}.
16
+ *
17
+ * @param budget - Output mode or explicit cap.
18
+ */
19
+ export function budgetTokens(budget) {
20
+ return typeof budget === "string" ? OUTPUT_BUDGET[budget] : Math.max(1, budget.maxTokens);
21
+ }
22
+ /**
23
+ * Trim `text` to fit `budget`; returns the possibly shortened text and whether
24
+ * truncation occurred. Under `brief` the suffix suggests `mode:"full"`; under
25
+ * `full` or an explicit cap it never does, since that would not widen anything.
26
+ *
27
+ * @param text - Text to budget.
28
+ * @param budget - Output mode (`brief` default) or `{ maxTokens }`.
14
29
  */
15
- export function applyTextBudget(text, mode = "brief") {
16
- const budget = OUTPUT_BUDGET[mode];
30
+ // Covers: req~find-response-budget~1
31
+ export function applyTextBudget(text, budget = "brief") {
32
+ const cap = budgetTokens(budget);
17
33
  const tokens = estimateTokens(text);
18
- if (tokens <= budget)
34
+ if (tokens <= cap)
19
35
  return { text, truncated: false, omittedChars: 0 };
20
- const suffixTokens = estimateTokens(TRUNCATION_SUFFIX);
21
- const bodyBudget = Math.max(1, budget - suffixTokens);
36
+ const suffix = budget === "brief" ? TRUNCATION_SUFFIX : FINAL_TRUNCATION_SUFFIX;
37
+ const suffixTokens = estimateTokens(suffix);
38
+ const bodyBudget = Math.max(1, cap - suffixTokens);
22
39
  const maxChars = bodyBudget * 4;
23
40
  const trimmed = text.slice(0, maxChars);
24
41
  return {
25
- text: trimmed + TRUNCATION_SUFFIX,
42
+ text: trimmed + suffix,
26
43
  truncated: true,
27
44
  omittedChars: text.length - maxChars,
28
45
  };
@@ -37,8 +54,11 @@ export function applyTextBudget(text, mode = "brief") {
37
54
  * @param truncated - Collector for explicit truncation records.
38
55
  */
39
56
  export function budgetExploreShape(value, mode, truncated) {
40
- const maxCallers = mode === "full" ? 40 : 12;
41
- const maxSourceLines = mode === "full" ? 120 : 40;
57
+ // Full mode sizes source to the full-mode token ceiling (~4500 tokens), so a
58
+ // typical long function arrives whole; its hints cannot point at mode:"full".
59
+ const full = mode === "full";
60
+ const maxCallers = full ? 40 : 12;
61
+ const maxSourceLines = full ? 400 : 40;
42
62
  const symbol = value.symbol;
43
63
  if (symbol && typeof symbol.source === "string") {
44
64
  const lines = symbol.source.split("\n");
@@ -48,7 +68,9 @@ export function budgetExploreShape(value, mode, truncated) {
48
68
  truncated.push({
49
69
  field: "symbol.source",
50
70
  omitted,
51
- hint: 'use mode:"full" or omit source from include',
71
+ hint: full
72
+ ? "read the remaining lines from the file by startLine/endLine"
73
+ : 'use mode:"full" or omit source from include',
52
74
  });
53
75
  }
54
76
  }
@@ -63,7 +85,9 @@ export function budgetExploreShape(value, mode, truncated) {
63
85
  truncated.push({
64
86
  field,
65
87
  omitted,
66
- hint: 'use mode:"full" or narrow with include',
88
+ hint: full
89
+ ? "use compass_impact for the full list"
90
+ : 'use mode:"full" or narrow with include',
67
91
  });
68
92
  }
69
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esneiderbravo/speclaw",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,26 +0,0 @@
1
- # Consumer template. init/update write this only when the path is missing.
2
- # Never the untrusted PR trigger. The verify job has no secrets.
3
- name: ⚖️ speclaw
4
- on: pull_request
5
-
6
- permissions: {}
7
-
8
- jobs:
9
- verify:
10
- name: ⚖️ Verify laws
11
- runs-on: ubuntu-latest
12
- permissions:
13
- contents: read
14
- security-events: write
15
- steps:
16
- - name: 📥 Checkout
17
- uses: actions/checkout@v5
18
- with:
19
- fetch-depth: 0
20
- - name: ⚖️ Verify
21
- uses: esneiderbravo/speclaw@v2
22
- - name: 🛡️ Upload SARIF
23
- uses: github/codeql-action/upload-sarif@v4
24
- if: always()
25
- with:
26
- sarif_file: speclaw.sarif