@geraldmaron/construct 1.0.23 → 1.1.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 (106) hide show
  1. package/README.md +0 -2
  2. package/bin/construct +189 -207
  3. package/lib/agent-instructions/inject.mjs +25 -4
  4. package/lib/audit-rules.mjs +127 -0
  5. package/lib/audit-skills.mjs +43 -1
  6. package/lib/beads-client.mjs +9 -0
  7. package/lib/beads-optimistic.mjs +23 -71
  8. package/lib/bridges/copilot-proxy.mjs +116 -0
  9. package/lib/cli-commands.mjs +5 -1
  10. package/lib/comment-lint.mjs +1 -1
  11. package/lib/config/schema.mjs +1 -1
  12. package/lib/document-extract/docling-client.mjs +16 -6
  13. package/lib/document-extract/docling-sidecar.py +32 -2
  14. package/lib/document-extract.mjs +37 -10
  15. package/lib/document-ingest.mjs +90 -5
  16. package/lib/embed/inbox.mjs +6 -3
  17. package/lib/embed/recommendation-store.mjs +7 -289
  18. package/lib/embed/reconcile.mjs +2 -2
  19. package/lib/embed/roadmap.mjs +16 -1
  20. package/lib/engine/consolidate.mjs +160 -3
  21. package/lib/engine/contradiction-judge.mjs +71 -0
  22. package/lib/engine/contradiction.mjs +74 -0
  23. package/lib/hooks/config-protection.mjs +4 -4
  24. package/lib/hooks/session-reflect.mjs +5 -1
  25. package/lib/host-capabilities.mjs +30 -0
  26. package/lib/ingest/docling-remote.mjs +90 -0
  27. package/lib/ingest/strategy.mjs +1 -1
  28. package/lib/init-unified.mjs +9 -13
  29. package/lib/intake/git-queue.mjs +195 -0
  30. package/lib/intake/queue.mjs +9 -16
  31. package/lib/logging/rotate.mjs +18 -0
  32. package/lib/mcp/server.mjs +124 -12
  33. package/lib/mcp/tool-budget.mjs +53 -0
  34. package/lib/mcp/tools/storage.mjs +2 -3
  35. package/lib/mcp-catalog.json +4 -4
  36. package/lib/mcp-manager.mjs +59 -3
  37. package/lib/observation-store.mjs +38 -166
  38. package/lib/ollama/capability-store.mjs +78 -0
  39. package/lib/ollama/provision-context.mjs +344 -0
  40. package/lib/opencode-config.mjs +148 -0
  41. package/lib/opencode-telemetry.mjs +7 -0
  42. package/lib/orchestration/runtime.mjs +3 -2
  43. package/lib/orchestration-policy.mjs +41 -6
  44. package/lib/platforms/capabilities.mjs +100 -0
  45. package/lib/prompt-composer.js +12 -8
  46. package/lib/reconcile/agent-instructions-rewrap.mjs +8 -4
  47. package/lib/reconcile/index.mjs +0 -2
  48. package/lib/reflect/extractor.mjs +14 -1
  49. package/lib/reflect/salience.mjs +65 -0
  50. package/lib/rules-delivery.mjs +122 -0
  51. package/lib/runtime/uv-bootstrap.mjs +32 -17
  52. package/lib/service-manager.mjs +79 -259
  53. package/lib/setup.mjs +44 -425
  54. package/lib/specialists/prompt-schema.mjs +162 -0
  55. package/lib/specialists/scaffold.mjs +109 -0
  56. package/lib/status.mjs +3 -6
  57. package/lib/storage/admin.mjs +48 -325
  58. package/lib/storage/backend.mjs +10 -57
  59. package/lib/storage/embeddings-engine.mjs +19 -5
  60. package/lib/storage/hybrid-query.mjs +15 -196
  61. package/lib/storage/sync.mjs +36 -177
  62. package/lib/storage/vector-client.mjs +256 -235
  63. package/lib/strategy-store.mjs +35 -286
  64. package/lib/telemetry/beads-fallback.mjs +40 -0
  65. package/lib/telemetry/hook-calls.mjs +138 -0
  66. package/lib/worker/entrypoint.mjs +6 -14
  67. package/package.json +6 -5
  68. package/personas/construct.md +1 -1
  69. package/platforms/capabilities.json +76 -0
  70. package/platforms/claude/settings.template.json +0 -7
  71. package/platforms/opencode/sync-config.mjs +121 -25
  72. package/rules/common/neurodivergent-output.md +1 -1
  73. package/rules/web/coding-style.md +8 -0
  74. package/rules/web/design-quality.md +8 -0
  75. package/rules/web/hooks.md +8 -0
  76. package/rules/web/patterns.md +8 -0
  77. package/rules/web/performance.md +8 -0
  78. package/rules/web/security.md +8 -0
  79. package/rules/web/testing.md +8 -0
  80. package/scripts/sync-specialists.mjs +174 -40
  81. package/specialists/prompts/cx-architect.md +20 -0
  82. package/specialists/prompts/cx-qa.md +1 -1
  83. package/specialists/prompts/cx-test-automation.md +12 -0
  84. package/specialists/registry.json +0 -8
  85. package/templates/docs/construct_guide.md +1 -1
  86. package/db/schema/001_init.sql +0 -40
  87. package/db/schema/002_pgvector.sql +0 -182
  88. package/db/schema/003_intake.sql +0 -47
  89. package/db/schema/003_observation_reconciliation.sql +0 -14
  90. package/db/schema/004_recommendations.sql +0 -46
  91. package/db/schema/005_strategy.sql +0 -21
  92. package/db/schema/006_graph.sql +0 -24
  93. package/db/schema/007_tags.sql +0 -30
  94. package/db/schema/008_skill_usage.sql +0 -24
  95. package/db/schema/009_scheduler.sql +0 -14
  96. package/db/schema/010_cx_scores.sql +0 -51
  97. package/lib/intake/postgres-queue.mjs +0 -240
  98. package/lib/reconcile/postgres-namespace.mjs +0 -102
  99. package/lib/services/local-postgres.mjs +0 -15
  100. package/lib/storage/backup.mjs +0 -347
  101. package/lib/storage/migrations.mjs +0 -187
  102. package/lib/storage/postgres-backup.mjs +0 -124
  103. package/lib/storage/sql-store.mjs +0 -45
  104. package/lib/storage/store-version.mjs +0 -115
  105. package/lib/storage/unified-storage.mjs +0 -550
  106. package/lib/storage/vector-store.mjs +0 -100
@@ -0,0 +1,76 @@
1
+ {
2
+ "$schema": "../schemas/platform-capabilities.schema.json",
3
+ "version": 1,
4
+ "hosts": {
5
+ "claude": {
6
+ "displayName": "Claude Code",
7
+ "hasNativeSubagents": false,
8
+ "instructionsOnly": false,
9
+ "supportsMcp": true,
10
+ "configFormat": "json",
11
+ "localModelProvisioning": "none",
12
+ "hooks": {
13
+ "supported": true,
14
+ "globalAllowlist": [
15
+ "pre:bash:block-no-verify",
16
+ "pre:bash:guard-dangerous",
17
+ "pre:edit:config-protection",
18
+ "pre:edit-guard",
19
+ "post:edit:json-validate",
20
+ "post:edit:scan-secrets"
21
+ ]
22
+ },
23
+ "globalMcpAllowlist": ["context7"]
24
+ },
25
+ "codex": {
26
+ "displayName": "Codex",
27
+ "hasNativeSubagents": false,
28
+ "instructionsOnly": false,
29
+ "supportsMcp": true,
30
+ "configFormat": "toml",
31
+ "localModelProvisioning": "none",
32
+ "hooks": { "supported": false, "globalAllowlist": [] },
33
+ "globalMcpAllowlist": []
34
+ },
35
+ "copilot": {
36
+ "displayName": "Copilot",
37
+ "hasNativeSubagents": false,
38
+ "instructionsOnly": true,
39
+ "supportsMcp": false,
40
+ "configFormat": "markdown",
41
+ "localModelProvisioning": "none",
42
+ "hooks": { "supported": false, "globalAllowlist": [] },
43
+ "globalMcpAllowlist": []
44
+ },
45
+ "opencode": {
46
+ "displayName": "OpenCode",
47
+ "hasNativeSubagents": true,
48
+ "instructionsOnly": false,
49
+ "supportsMcp": true,
50
+ "configFormat": "json",
51
+ "localModelProvisioning": "modelfile",
52
+ "hooks": { "supported": false, "globalAllowlist": [] },
53
+ "globalMcpAllowlist": []
54
+ },
55
+ "vscode": {
56
+ "displayName": "VS Code",
57
+ "hasNativeSubagents": true,
58
+ "instructionsOnly": false,
59
+ "supportsMcp": true,
60
+ "configFormat": "json",
61
+ "localModelProvisioning": "none",
62
+ "hooks": { "supported": false, "globalAllowlist": [] },
63
+ "globalMcpAllowlist": []
64
+ },
65
+ "cursor": {
66
+ "displayName": "Cursor",
67
+ "hasNativeSubagents": true,
68
+ "instructionsOnly": false,
69
+ "supportsMcp": true,
70
+ "configFormat": "json",
71
+ "localModelProvisioning": "none",
72
+ "hooks": { "supported": false, "globalAllowlist": [] },
73
+ "globalMcpAllowlist": []
74
+ }
75
+ }
76
+ }
@@ -547,13 +547,6 @@
547
547
  "memory": {
548
548
  "type": "http",
549
549
  "url": "http://127.0.0.1:8765/"
550
- },
551
- "playwright": {
552
- "command": "npx",
553
- "args": [
554
- "-y",
555
- "@playwright/mcp@latest"
556
- ]
557
550
  }
558
551
  }
559
552
  }
@@ -1,13 +1,51 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * sync-config.mjs — Fetch free models from OpenRouter and sync into OpenCode config.
4
- * Run periodically or during install to keep the free model list current.
3
+ * sync-config.mjs — Fetch free models from OpenRouter and local models from Ollama,
4
+ * and sync into OpenCode config.
5
5
  */
6
6
  import fs from "node:fs";
7
- import { readOpenCodeConfig, writeOpenCodeConfig, findOpenCodeConfigPath } from "../lib/opencode-config.mjs";
7
+ import { spawnSync } from "node:child_process";
8
+ import { readOpenCodeConfig, writeOpenCodeConfig, findOpenCodeConfigPath } from "../../lib/opencode-config.mjs";
9
+ import { ensureLocalContextVariants, modelDigest } from "../../lib/ollama/provision-context.mjs";
10
+ import { isKnownCollapsed } from "../../lib/ollama/capability-store.mjs";
11
+
12
+ const LOCAL_NUM_CTX = Number(process.env.CONSTRUCT_LOCAL_NUM_CTX) || 32768;
8
13
 
9
14
  const configPath = findOpenCodeConfigPath();
10
15
 
16
+ async function fetchLocalOllamaModels() {
17
+ try {
18
+ const r = spawnSync("ollama", ["list"], { encoding: "utf8" });
19
+ if (r.status !== 0) return [];
20
+
21
+ const lines = r.stdout.trim().split("\n").slice(1);
22
+ const models = lines.map(line => {
23
+ const parts = line.split(/\s+/).filter(Boolean);
24
+ const id = parts[0];
25
+ const size = parts[2];
26
+ return { id, name: id, size };
27
+ });
28
+
29
+ // Identify redundant tags (same family, many tags)
30
+ const familyMap = new Map();
31
+ for (const m of models) {
32
+ const family = m.id.split(":")[0];
33
+ if (!familyMap.has(family)) familyMap.set(family, []);
34
+ familyMap.get(family).push(m);
35
+ }
36
+
37
+ for (const [family, tags] of familyMap) {
38
+ if (tags.length > 2) {
39
+ console.log(`[cleanup] Potential redundant tags for ${family}: ${tags.map(t => t.id).join(", ")}`);
40
+ }
41
+ }
42
+
43
+ return models;
44
+ } catch {
45
+ return [];
46
+ }
47
+ }
48
+
11
49
  async function fetchFreeModels() {
12
50
  try {
13
51
  const res = await fetch("https://openrouter.ai/api/v1/models");
@@ -31,39 +69,97 @@ async function main() {
31
69
 
32
70
  const { config } = readOpenCodeConfig();
33
71
  const freeModels = await fetchFreeModels();
72
+ const localModels = await fetchLocalOllamaModels();
34
73
 
35
- if (freeModels.length === 0) {
36
- console.log("No free models fetched. Config unchanged.");
74
+ if (freeModels.length === 0 && localModels.length === 0) {
75
+ console.log("No models fetched. Config unchanged.");
37
76
  return;
38
77
  }
39
78
 
40
79
  if (!config.provider) config.provider = {};
41
- if (!config.provider.openrouter) {
42
- config.provider.openrouter = {
43
- npm: "@ai-sdk/openai-compatible",
44
- name: "OpenRouter",
45
- options: {
46
- baseURL: "https://openrouter.ai/api/v1",
47
- headers: {}
48
- },
49
- models: {}
50
- };
80
+
81
+ // Sync OpenRouter Free Models
82
+ if (freeModels.length > 0) {
83
+ if (!config.provider.openrouter) {
84
+ config.provider.openrouter = {
85
+ npm: "@ai-sdk/openai-compatible",
86
+ name: "OpenRouter",
87
+ options: {
88
+ baseURL: "https://openrouter.ai/api/v1",
89
+ headers: {}
90
+ },
91
+ models: {}
92
+ };
93
+ }
94
+ const existingOR = config.provider.openrouter.models ?? {};
95
+ for (const model of freeModels) {
96
+ const cleanName = model.name.replace(/\s*\(free\)/gi, "").trim();
97
+ existingOR[model.id] = { name: `[free] ${cleanName}` };
98
+ }
99
+ config.provider.openrouter.models = Object.fromEntries(
100
+ Object.entries(existingOR).sort((a, b) => (a[1].name ?? a[0]).localeCompare(b[1].name ?? b[0]))
101
+ );
51
102
  }
52
103
 
53
- const existingModels = config.provider.openrouter.models ?? {};
54
- const merged = { ...existingModels };
55
- for (const model of freeModels) {
56
- if (!merged[model.id]) {
57
- merged[model.id] = { name: `${model.name} (free)` };
104
+ // Sync Local Ollama Models
105
+ if (localModels.length > 0) {
106
+ if (!config.provider.ollama) {
107
+ config.provider.ollama = {
108
+ npm: "@ai-sdk/openai-compatible",
109
+ name: "Ollama",
110
+ options: {
111
+ baseURL: "http://localhost:11434/v1"
112
+ },
113
+ models: {}
114
+ };
58
115
  }
59
- }
116
+ // A raw model runs at Ollama's 4096 default over the /v1 path, which a Construct
117
+ // session's tool schemas overrun. Provision a context-extended Modelfile variant
118
+ // for each tool-capable model lacking a baked num_ctx (any size — capability does
119
+ // not track size) and register the variant in place of the raw tag so the model
120
+ // OpenCode actually talks to has a real context window.
121
+
122
+ const { mapping: variantMap, actions } = ensureLocalContextVariants({ numCtx: LOCAL_NUM_CTX });
123
+ for (const a of actions) {
124
+ if (a.action === "created" || a.action === "would-create") console.log(`[ollama] context variant ${a.action}: ${a.variant} (num_ctx ${LOCAL_NUM_CTX})`);
125
+ }
126
+
127
+ const existingLocal = config.provider.ollama.models ?? {};
128
+ for (const model of localModels) {
129
+ const registerId = variantMap[model.id] || model.id;
130
+ // Register the context-extended variant in place of the raw tag so the model
131
+ // picker surfaces the windowed option, not the one Ollama serves at 4096.
60
132
 
61
- config.provider.openrouter.models = Object.fromEntries(
62
- Object.entries(merged).sort((a, b) => (a[1].name ?? a[0]).localeCompare(b[1].name ?? b[0]))
63
- );
133
+ if (registerId !== model.id) delete existingLocal[model.id];
134
+ existingLocal[registerId] = {
135
+ name: registerId.replace(/\s*\(local\)/gi, "").trim(),
136
+ family: registerId.includes("qwen") ? "qwen2" : "llama",
137
+ tool_call: true
138
+ };
139
+
140
+ // Capability honesty: a model the probe recorded as COLLAPSED (digest still
141
+ // matching) word-salads on the agentic loop. Warn rather than hide — the
142
+ // user keeps the choice and can re-probe — so a stale or false verdict never
143
+ // silently strands a working model.
144
+ if (isKnownCollapsed(model.id, modelDigest(model.id))) {
145
+ console.log(`[ollama] WARNING: ${model.id} probed COLLAPSED — not agentic-capable. Re-probe: construct doctor --probe-local`);
146
+ }
147
+ }
148
+ config.provider.ollama.models = Object.fromEntries(
149
+ Object.entries(existingLocal).sort((a, b) => (a[1].name ?? a[0]).localeCompare(b[1].name ?? b[0]))
150
+ );
151
+
152
+ // Agentic coherence is model-specific and not predictable from size: some small
153
+ // coder models collapse into repetition on an agentic system prompt while others
154
+ // run the same Construct payload cleanly. Surface the empirical probe rather than
155
+ // silently registering a model that will produce word salad.
156
+
157
+ console.log("[ollama] Verify a model handles agentic prompts before relying on it:");
158
+ console.log("[ollama] node lib/ollama/provision-context.mjs --probe --model=<id>");
159
+ }
64
160
 
65
161
  writeOpenCodeConfig(config, configPath);
66
- console.log(`Synced ${freeModels.length} free models from OpenRouter.`);
162
+ console.log(`Synced ${freeModels.length} free models and ${localModels.length} local models.`);
67
163
  }
68
164
 
69
165
  main();
@@ -28,7 +28,7 @@ Use headings in order (H2, then H3) and do not skip or nest deeper. One `h1`-equ
28
28
 
29
29
  ## 3. Scannable chunks
30
30
 
31
- Short paragraphs. Bulleted or numbered lists in place of long sentences that pack several facts together. White space between sections. Prefer three short lines over one dense one.
31
+ Short paragraphs with white space between sections; prefer three short lines over one dense one. Use a bulleted or numbered list for genuinely parallel items — a set of options, steps, or independent facts — where scanning is the point. Keep reasoning, cause-and-effect, and narrative in prose: a wall of bullets fragments the logic that connects the points and is harder to follow, not easier. Bullets for what scans, sentences for what reasons.
32
32
 
33
33
  ## 4. Plain, literal language
34
34
 
@@ -1,5 +1,13 @@
1
1
  ---
2
2
  description: Construct web coding style rule. Use when writing or reviewing web code that involves coding style.
3
+ paths:
4
+ - "**/*.tsx"
5
+ - "**/*.jsx"
6
+ - "**/*.css"
7
+ - "**/*.scss"
8
+ - "**/*.html"
9
+ - "**/*.vue"
10
+ - "**/*.svelte"
3
11
  ---
4
12
  > This file extends [common/coding-style.md](../common/coding-style.md) with web-specific frontend content.
5
13
 
@@ -1,5 +1,13 @@
1
1
  ---
2
2
  description: Construct web design quality rule. Use when writing or reviewing web code that involves design quality.
3
+ paths:
4
+ - "**/*.tsx"
5
+ - "**/*.jsx"
6
+ - "**/*.css"
7
+ - "**/*.scss"
8
+ - "**/*.html"
9
+ - "**/*.vue"
10
+ - "**/*.svelte"
3
11
  ---
4
12
  > This file extends [common/patterns.md](../common/patterns.md) with web-specific design-quality guidance.
5
13
 
@@ -1,5 +1,13 @@
1
1
  ---
2
2
  description: Prefer project-local tooling.
3
+ paths:
4
+ - "**/*.tsx"
5
+ - "**/*.jsx"
6
+ - "**/*.css"
7
+ - "**/*.scss"
8
+ - "**/*.html"
9
+ - "**/*.vue"
10
+ - "**/*.svelte"
3
11
  ---
4
12
  # Web Hooks
5
13
 
@@ -1,5 +1,13 @@
1
1
  ---
2
2
  description: Construct web patterns rule. Use when writing or reviewing web code that involves patterns.
3
+ paths:
4
+ - "**/*.tsx"
5
+ - "**/*.jsx"
6
+ - "**/*.css"
7
+ - "**/*.scss"
8
+ - "**/*.html"
9
+ - "**/*.vue"
10
+ - "**/*.svelte"
3
11
  ---
4
12
  > This file extends [common/patterns.md](../common/patterns.md) with web-specific patterns.
5
13
 
@@ -1,5 +1,13 @@
1
1
  ---
2
2
  description: Construct web performance rule. Use when writing or reviewing web code that involves performance.
3
+ paths:
4
+ - "**/*.tsx"
5
+ - "**/*.jsx"
6
+ - "**/*.css"
7
+ - "**/*.scss"
8
+ - "**/*.html"
9
+ - "**/*.vue"
10
+ - "**/*.svelte"
3
11
  ---
4
12
  > This file extends [common/performance.md](../common/performance.md) with web-specific performance content.
5
13
 
@@ -1,5 +1,13 @@
1
1
  ---
2
2
  description: Construct web security rule. Use when writing or reviewing web code that involves security.
3
+ paths:
4
+ - "**/*.tsx"
5
+ - "**/*.jsx"
6
+ - "**/*.css"
7
+ - "**/*.scss"
8
+ - "**/*.html"
9
+ - "**/*.vue"
10
+ - "**/*.svelte"
3
11
  ---
4
12
  > This file extends [common/security.md](../common/security.md) with web-specific security content.
5
13
 
@@ -1,5 +1,13 @@
1
1
  ---
2
2
  description: Construct web testing rule. Use when writing or reviewing web code that involves testing.
3
+ paths:
4
+ - "**/*.tsx"
5
+ - "**/*.jsx"
6
+ - "**/*.css"
7
+ - "**/*.scss"
8
+ - "**/*.html"
9
+ - "**/*.vue"
10
+ - "**/*.svelte"
3
11
  ---
4
12
  > This file extends [common/testing.md](../common/testing.md) with web-specific testing content.
5
13