@geraldmaron/construct 1.4.0 → 1.4.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.
Files changed (65) hide show
  1. package/bin/construct +2 -1
  2. package/bin/construct-postinstall.mjs +27 -2
  3. package/config/tag-vocabulary.json +264 -0
  4. package/lib/config/schema.mjs +1 -1
  5. package/lib/doctor/diagnosis.mjs +20 -0
  6. package/lib/doctor/index.mjs +5 -0
  7. package/lib/embed/worker.mjs +5 -0
  8. package/lib/env-config.mjs +10 -2
  9. package/lib/export-validate.mjs +34 -2
  10. package/lib/hooks/guard-bash.mjs +3 -1
  11. package/lib/host/readiness.mjs +109 -0
  12. package/lib/host-disposition.mjs +10 -1
  13. package/lib/install/stage-project.mjs +41 -4
  14. package/lib/intake/prepare.mjs +2 -0
  15. package/lib/mcp/destructive-approval.mjs +57 -0
  16. package/lib/mcp/server.mjs +209 -35
  17. package/lib/mcp/tool-rate-limit.mjs +47 -0
  18. package/lib/mcp/tool-safety.mjs +94 -0
  19. package/lib/mcp/tool-surface-parity.mjs +60 -0
  20. package/lib/mcp/tools/orchestration-run.mjs +45 -7
  21. package/lib/mcp/tools/project.mjs +25 -8
  22. package/lib/mcp/tools/storage.mjs +9 -1
  23. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  24. package/lib/mcp/tools/web-search.mjs +6 -44
  25. package/lib/mcp-platform-config.mjs +27 -18
  26. package/lib/oracle/daemon-entry.mjs +6 -0
  27. package/lib/orchestration/runtime.mjs +81 -42
  28. package/lib/orchestration/web-capability.mjs +59 -0
  29. package/lib/orchestration/worker.mjs +263 -19
  30. package/lib/orchestration-policy.mjs +36 -0
  31. package/lib/output-quality.mjs +61 -2
  32. package/lib/path-policy.mjs +56 -0
  33. package/lib/providers/secret-audit-wiring.mjs +35 -18
  34. package/lib/providers/secret-resolver.mjs +28 -13
  35. package/lib/registry/catalog.mjs +6 -0
  36. package/lib/registry/loader.mjs +7 -1
  37. package/lib/registry/validate.mjs +3 -3
  38. package/lib/sandbox.mjs +1 -1
  39. package/lib/service-manager.mjs +59 -9
  40. package/lib/storage/admin.mjs +7 -2
  41. package/package.json +6 -1
  42. package/registry/agent-manifest.json +117 -0
  43. package/registry/capabilities.json +1880 -0
  44. package/schemas/brand-voice.schema.json +24 -0
  45. package/schemas/capability-registry.schema.json +72 -0
  46. package/schemas/certification-run.schema.json +130 -0
  47. package/schemas/demo-recording.schema.json +46 -0
  48. package/schemas/eval-dataset.schema.json +79 -0
  49. package/schemas/execution-capability-profile.schema.json +46 -0
  50. package/schemas/execution-policy.schema.json +114 -0
  51. package/schemas/improvement-proposal.schema.json +65 -0
  52. package/schemas/mcp-tool-output.schema.json +61 -0
  53. package/schemas/platform-capabilities.schema.json +83 -0
  54. package/schemas/project-config.schema.json +227 -0
  55. package/schemas/project-demo.schema.json +60 -0
  56. package/schemas/provider-behavior-matrix.schema.json +91 -0
  57. package/schemas/scope.schema.json +197 -0
  58. package/schemas/specialist-trace.schema.json +107 -0
  59. package/schemas/team.schema.json +99 -0
  60. package/schemas/unified-registry.schema.json +548 -0
  61. package/scripts/sync-specialists.mjs +135 -12
  62. package/specialists/org/specialists/cx-researcher.json +1 -0
  63. package/specialists/prompts/cx-researcher.md +9 -8
  64. package/vendor/pandoc-ext/README.md +3 -0
  65. package/vendor/pandoc-ext/diagram.lua +687 -0
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { spawnSync } from 'node:child_process';
14
- import { existsSync, copyFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
14
+ import { existsSync, readFileSync, copyFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
15
15
  import path from 'node:path';
16
16
 
17
17
  export function stageProjectAdapters({ projectRoot, packageRoot, pkgVersion, log, hosts = null }) {
@@ -27,7 +27,7 @@ export function stageProjectAdapters({ projectRoot, packageRoot, pkgVersion, log
27
27
 
28
28
  if (!existsSync(syncScript)) {
29
29
  emit(`sync-specialists.mjs not found at ${syncScript}; skipping adapter sync`);
30
- return { staged: true, synced: false };
30
+ return finishStage({ projectRoot, pkgVersion, synced: false, reason: 'sync-script-missing' });
31
31
  }
32
32
 
33
33
  // A `hosts` array restricts which adapter sets sync writes (construct-4xy6);
@@ -45,9 +45,46 @@ export function stageProjectAdapters({ projectRoot, packageRoot, pkgVersion, log
45
45
 
46
46
  if (result.status !== 0) {
47
47
  emit(`sync failed (exit ${result.status}); project left in a clean state`);
48
- return { staged: true, synced: false };
48
+ return finishStage({ projectRoot, pkgVersion, synced: false, reason: `sync-exit-${result.status}` });
49
49
  }
50
- return { staged: true, synced: true };
50
+ return finishStage({ projectRoot, pkgVersion, synced: true, reason: null });
51
+ }
52
+
53
+ const STAGE_STATE_FILE = 'stage-state.json';
54
+
55
+ // Record the staging outcome under .construct/ so a half-stage (launcher present,
56
+ // adapters not synced) is detectable and repairable instead of re-derived from
57
+ // filesystem heuristics. The marker write is best-effort — a state-write failure must
58
+ // never turn a successful stage into a reported failure.
59
+
60
+ function finishStage({ projectRoot, pkgVersion, synced, reason }) {
61
+ const dotConstruct = path.join(projectRoot, '.construct');
62
+ try {
63
+ mkdirSync(dotConstruct, { recursive: true });
64
+ const state = { staged: true, synced, reason, pkgVersion: pkgVersion ?? null, ts: new Date().toISOString() };
65
+ writeFileSync(path.join(dotConstruct, STAGE_STATE_FILE), JSON.stringify(state, null, 2) + '\n');
66
+ } catch { /* marker is advisory */ }
67
+ return { staged: true, synced };
68
+ }
69
+
70
+ export function readStageState(projectRoot) {
71
+ try {
72
+ return JSON.parse(readFileSync(path.join(projectRoot, '.construct', STAGE_STATE_FILE), 'utf8'));
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ // Re-drive a half-staged project to a synced state. stageProjectAdapters is idempotent,
79
+ // so re-running the sync is the repair; an already-synced project is a no-op.
80
+
81
+ export function repairStagedProject({ projectRoot, packageRoot, pkgVersion, log, hosts = null }) {
82
+ const state = readStageState(projectRoot);
83
+ if (state && state.synced === true) {
84
+ return { staged: true, synced: true, repaired: false };
85
+ }
86
+ const result = stageProjectAdapters({ projectRoot, packageRoot, pkgVersion, log, hosts });
87
+ return { ...result, repaired: result.synced === true };
51
88
  }
52
89
 
53
90
  function ensureProjectLauncher({ projectRoot, templateDir, pkgVersion }) {
@@ -123,6 +123,8 @@ export async function prepareIntakeForIngestedFile({
123
123
  const tagSuggestions = suggestTags(triage, related, vocab);
124
124
 
125
125
  const baseEntry = {
126
+ // OWASP LLM01: externally-ingested document bodies are untrusted data, not instructions
127
+ trust: 'untrusted',
126
128
  intake: {
127
129
  sourcePath: ingestedFile.sourcePath,
128
130
  outputPath: ingestedFile.outputPath,
@@ -0,0 +1,57 @@
1
+ /**
2
+ * lib/mcp/destructive-approval.mjs — out-of-band approval tokens for destructive MCP tools.
3
+ *
4
+ * storage_reset and delete_ingested_artifacts are reachable only through the model's
5
+ * argument channel, so a confirm flag the model itself supplies cannot authorize
6
+ * irreversible deletion. Tokens are issued out-of-band by an operator action (never the
7
+ * model), persisted with 0600 under the user state dir — outside any project root, so a
8
+ * root-contained file tool cannot read them — and consumed one-time. consumeApprovalToken
9
+ * returns false unless a live, unexpired token for the scope matches, and never writes
10
+ * when no token is supplied.
11
+ */
12
+ import { randomBytes } from 'node:crypto';
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { doctorRoot } from '../config/xdg.mjs';
16
+
17
+ const TOKEN_TTL_MS = 5 * 60 * 1000;
18
+
19
+ function storePath(env) {
20
+ return path.join(doctorRoot(undefined, env), 'destructive-approvals.json');
21
+ }
22
+
23
+ function readStore(file) {
24
+ try {
25
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
26
+ return Array.isArray(parsed) ? parsed : [];
27
+ } catch {
28
+ return [];
29
+ }
30
+ }
31
+
32
+ function writeStore(file, tokens) {
33
+ fs.mkdirSync(path.dirname(file), { recursive: true });
34
+ fs.writeFileSync(file, JSON.stringify(tokens), { mode: 0o600 });
35
+ try { fs.chmodSync(file, 0o600); } catch { /* non-posix filesystems */ }
36
+ }
37
+
38
+ export function issueApprovalToken(scope, { env = process.env, now = Date.now() } = {}) {
39
+ if (typeof scope !== 'string' || !scope) throw new Error('scope required');
40
+ const file = storePath(env);
41
+ const token = randomBytes(24).toString('hex');
42
+ const live = readStore(file).filter((t) => t.expiresAt > now);
43
+ live.push({ token, scope, expiresAt: now + TOKEN_TTL_MS });
44
+ writeStore(file, live);
45
+ return token;
46
+ }
47
+
48
+ export function consumeApprovalToken(scope, token, { env = process.env, now = Date.now() } = {}) {
49
+ if (typeof token !== 'string' || !token) return false;
50
+ const file = storePath(env);
51
+ const tokens = readStore(file);
52
+ const idx = tokens.findIndex((t) => t.scope === scope && t.token === token && t.expiresAt > now);
53
+ if (idx === -1) return false;
54
+ tokens.splice(idx, 1);
55
+ writeStore(file, tokens.filter((t) => t.expiresAt > now));
56
+ return true;
57
+ }