@danielblomma/cortex-mcp 2.0.16 → 2.0.19

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 (52) hide show
  1. package/README.md +6 -4
  2. package/bin/cortex.mjs +75 -10
  3. package/package.json +2 -1
  4. package/scaffold/.context/config.yaml +2 -3
  5. package/scaffold/.githooks/_cortex-update-runner.sh +2 -2
  6. package/scaffold/mcp/package-lock.json +59 -60
  7. package/scaffold/mcp/package.json +2 -2
  8. package/scaffold/mcp/src/cli/stage.ts +15 -18
  9. package/scaffold/mcp/src/core/validators/builtins.ts +24 -4
  10. package/scaffold/mcp/src/core/workflow/index.ts +1 -0
  11. package/scaffold/mcp/src/core/workflow/mcp-tools.ts +15 -25
  12. package/scaffold/mcp/src/core/workflow/resolution.ts +113 -0
  13. package/scaffold/mcp/src/embeddings.ts +1 -1
  14. package/scaffold/mcp/src/enterprise/index.ts +16 -1
  15. package/scaffold/mcp/src/enterprise/reviews/policy-selection.ts +65 -0
  16. package/scaffold/mcp/src/enterprise/reviews/push.ts +55 -4
  17. package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +305 -0
  18. package/scaffold/mcp/src/enterprise/tools/enterprise.ts +40 -47
  19. package/scaffold/mcp/src/enterprise/tools/harness.ts +2 -0
  20. package/scaffold/mcp/src/enterprise/workflow/state.ts +21 -0
  21. package/scaffold/mcp/src/graph.ts +3 -3
  22. package/scaffold/mcp/tests/enterprise-review-trust.test.mjs +318 -0
  23. package/scaffold/mcp/tests/fixtures/org-skillz-contract.json +59 -0
  24. package/scaffold/mcp/tests/fixtures/review-trust-contract.json +111 -0
  25. package/scaffold/mcp/tests/review-policy-selection.test.mjs +85 -0
  26. package/scaffold/mcp/tests/review-trust-contract.test.mjs +167 -0
  27. package/scaffold/mcp/tests/review-validator-source.test.mjs +52 -0
  28. package/scaffold/mcp/tests/skill-sync-checker.test.mjs +277 -0
  29. package/scaffold/mcp/tests/workflow-cli.test.mjs +102 -1
  30. package/scaffold/mcp/tests/workflow-mcp-tools.test.mjs +22 -0
  31. package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +56 -0
  32. package/scaffold/scripts/bootstrap.sh +9 -8
  33. package/scaffold/scripts/context.sh +1 -1
  34. package/scaffold/scripts/dashboard.mjs +3 -1
  35. package/scaffold/scripts/dashboard.sh +1 -1
  36. package/scaffold/scripts/doctor.sh +2 -1
  37. package/scaffold/scripts/embed.sh +2 -1
  38. package/scaffold/scripts/ingest.mjs +3 -1
  39. package/scaffold/scripts/ingest.sh +3 -2
  40. package/scaffold/scripts/install-git-hooks.sh +2 -1
  41. package/scaffold/scripts/load-kuzu.sh +2 -2
  42. package/scaffold/scripts/load-ryu.sh +2 -1
  43. package/scaffold/scripts/memory-compile.mjs +2 -2
  44. package/scaffold/scripts/memory-compile.sh +3 -2
  45. package/scaffold/scripts/memory-lint.mjs +2 -2
  46. package/scaffold/scripts/memory-lint.sh +3 -2
  47. package/scaffold/scripts/parsers/node_modules/.package-lock.json +0 -1
  48. package/scaffold/scripts/parsers/package-lock.json +0 -1
  49. package/scaffold/scripts/refresh.sh +2 -2
  50. package/scaffold/scripts/status.sh +6 -5
  51. package/scaffold/scripts/update-context.sh +5 -5
  52. package/scaffold/scripts/watch.sh +7 -8
package/README.md CHANGED
@@ -97,7 +97,7 @@ cortex init --bootstrap
97
97
 
98
98
  This will:
99
99
 
100
- - scaffold `.context/`, `scripts/`, `mcp/`, `.githooks/`, and docs files
100
+ - scaffold `.context/`, `.context/scripts/`, `.context/mcp/`, `.githooks/`, and docs files
101
101
  - activate git hooks for checkout, pull/merge, commit, and rewrite events
102
102
  - build and prepare the local MCP server
103
103
  - try to auto-register MCP connections for Claude/Codex (if installed)
@@ -346,11 +346,13 @@ This repository includes two GitHub Actions workflows:
346
346
  - Triggers on tag push `v*.*.*`
347
347
  - Verifies tag/version sync
348
348
  - Runs root tests + MCP build/tests
349
- - Publishes `@danielblomma/cortex-mcp` to npm
349
+ - Publishes `@danielblomma/cortex-mcp` to npm via npm trusted publishing (GitHub OIDC)
350
350
 
351
- Required GitHub secret:
351
+ Required npm configuration:
352
352
 
353
- - `NPM_TOKEN` (npm automation token with publish rights for `@danielblomma/cortex-mcp`)
353
+ - Configure a trusted publisher for `@danielblomma/cortex-mcp` on npmjs.com
354
+ - Use GitHub Actions publisher `DanielBlomma/cortex`
355
+ - Workflow filename must match `release-publish.yml`
354
356
 
355
357
  ## Limitations
356
358
 
package/bin/cortex.mjs CHANGED
@@ -27,11 +27,14 @@ const PACKAGE_JSON_PATH = path.join(PACKAGE_ROOT, "package.json");
27
27
  // only the three editable config files". Generated artifacts (db,
28
28
  // embeddings, cache, hooks, mcp/, govern.local.json) never land in git.
29
29
  const MCP_PROJECT_REL = path.join(".context", "mcp");
30
+ const CONTEXT_SCRIPTS_REL = path.join(".context", "scripts");
30
31
 
32
+ // `.context/*` (not `.context/`) so the !-negations below actually re-include
33
+ // the config files — git can't re-include children of an excluded directory.
31
34
  const GITIGNORE_LINES = [
32
35
  "",
33
36
  "# Cortex local storage",
34
- ".context/",
37
+ ".context/*",
35
38
  "!.context/config.yaml",
36
39
  "!.context/rules.yaml",
37
40
  "!.context/ontology.cypher",
@@ -461,12 +464,73 @@ function migrateLegacyMcpLocation(targetDir) {
461
464
  );
462
465
  }
463
466
 
467
+ const LEGACY_SCRIPT_ENTRIES = [
468
+ "bootstrap.sh",
469
+ "context.sh",
470
+ "dashboard.mjs",
471
+ "dashboard.sh",
472
+ "doctor.sh",
473
+ "embed.sh",
474
+ "ingest.mjs",
475
+ "ingest.sh",
476
+ "install-git-hooks.sh",
477
+ "load-kuzu.sh",
478
+ "load-ryu.sh",
479
+ "memory-compile.mjs",
480
+ "memory-compile.sh",
481
+ "memory-lint.mjs",
482
+ "memory-lint.sh",
483
+ "refresh.sh",
484
+ "status.sh",
485
+ "update-context.sh",
486
+ "watch.sh",
487
+ "lib",
488
+ "parsers"
489
+ ];
490
+
491
+ function looksLikeLegacyCortexScriptsDir(scriptsDir) {
492
+ const contextScript = path.join(scriptsDir, "context.sh");
493
+ if (!fs.existsSync(contextScript)) {
494
+ return false;
495
+ }
496
+ try {
497
+ const contents = fs.readFileSync(contextScript, "utf8");
498
+ return contents.includes("bootstrap)") && contents.includes("graph-load)") && contents.includes("memory-lint)");
499
+ } catch {
500
+ return false;
501
+ }
502
+ }
503
+
504
+ function removeLegacyCortexScripts(targetDir) {
505
+ if (path.resolve(targetDir) === PACKAGE_ROOT) {
506
+ return;
507
+ }
508
+
509
+ const scriptsDir = path.join(targetDir, "scripts");
510
+ if (!looksLikeLegacyCortexScriptsDir(scriptsDir)) {
511
+ return;
512
+ }
513
+
514
+ for (const entry of LEGACY_SCRIPT_ENTRIES) {
515
+ fs.rmSync(path.join(scriptsDir, entry), { recursive: true, force: true });
516
+ }
517
+
518
+ try {
519
+ if (fs.existsSync(scriptsDir) && fs.readdirSync(scriptsDir).length === 0) {
520
+ fs.rmdirSync(scriptsDir);
521
+ }
522
+ } catch {
523
+ // Best effort. A user's own files in scripts/ must remain untouched.
524
+ }
525
+ }
526
+
464
527
  function installScaffold(targetDir, force) {
465
528
  migrateLegacyMcpLocation(targetDir);
529
+ removeLegacyCortexScripts(targetDir);
466
530
 
467
531
  const copyMap = [
468
532
  [path.join(SCAFFOLD_ROOT, ".context"), path.join(targetDir, ".context")],
469
- [path.join(SCAFFOLD_ROOT, "scripts"), path.join(targetDir, "scripts")],
533
+ [path.join(SCAFFOLD_ROOT, "scripts"), path.join(targetDir, CONTEXT_SCRIPTS_REL)],
470
534
  [path.join(SCAFFOLD_ROOT, "mcp"), path.join(targetDir, MCP_PROJECT_REL)],
471
535
  [path.join(SCAFFOLD_ROOT, ".githooks"), path.join(targetDir, ".githooks")]
472
536
  ];
@@ -720,7 +784,7 @@ async function connectMcpClients(targetDir, options = {}) {
720
784
  }
721
785
 
722
786
  async function maybeInstallGitHooks(targetDir) {
723
- const installScript = path.join(targetDir, "scripts", "install-git-hooks.sh");
787
+ const installScript = path.join(targetDir, CONTEXT_SCRIPTS_REL, "install-git-hooks.sh");
724
788
  if (!fs.existsSync(installScript)) {
725
789
  return false;
726
790
  }
@@ -758,16 +822,17 @@ function isTruthyEnv(value) {
758
822
  function canAutoInitialize(targetDir) {
759
823
  // Legacy mcp/ at root no longer counted — pre-v2.0.5 projects are migrated
760
824
  // by installScaffold rather than blocking auto-init.
761
- const scaffoldPaths = [".context", "scripts", ".githooks"].map((entry) => path.join(targetDir, entry));
825
+ const scaffoldPaths = [".context", ".githooks"].map((entry) => path.join(targetDir, entry));
762
826
  return scaffoldPaths.every((entryPath) => !fs.existsSync(entryPath));
763
827
  }
764
828
 
765
829
  function isScaffoldOutOfDate(targetDir) {
766
- const contextScript = path.join(targetDir, "scripts", "context.sh");
830
+ const contextScript = path.join(targetDir, CONTEXT_SCRIPTS_REL, "context.sh");
831
+ const legacyContextScript = path.join(targetDir, "scripts", "context.sh");
767
832
  if (!fs.existsSync(contextScript)) {
768
- return false;
833
+ return fs.existsSync(legacyContextScript);
769
834
  }
770
- const doctorScript = path.join(targetDir, "scripts", "doctor.sh");
835
+ const doctorScript = path.join(targetDir, CONTEXT_SCRIPTS_REL, "doctor.sh");
771
836
  if (!fs.existsSync(doctorScript)) {
772
837
  return true;
773
838
  }
@@ -812,7 +877,7 @@ async function maybeMigrateScaffold(targetDir, command) {
812
877
 
813
878
  console.error(
814
879
  `[cortex] scaffold in ${targetDir} is out of date ` +
815
- `(missing scripts/doctor.sh, .context/mcp/package.json, doctor subcommand in context.sh, ` +
880
+ `(missing .context/scripts/doctor.sh, .context/mcp/package.json, doctor subcommand in context.sh, ` +
816
881
  `or carries a legacy mcp/ directory at the project root).`
817
882
  );
818
883
 
@@ -881,7 +946,7 @@ async function ensureProjectInitializedForMcp(targetDir) {
881
946
  }
882
947
 
883
948
  async function runContextCommand(cwd, contextArgs) {
884
- const contextScript = path.join(cwd, "scripts", "context.sh");
949
+ const contextScript = path.join(cwd, CONTEXT_SCRIPTS_REL, "context.sh");
885
950
  if (!fs.existsSync(contextScript)) {
886
951
  throw new Error(`Missing ${contextScript}. Run 'cortex init' first.`);
887
952
  }
@@ -915,7 +980,7 @@ async function run() {
915
980
  await maybeInstallGitHooks(target);
916
981
 
917
982
  console.log(`[cortex] initialized in ${target}`);
918
- console.log("[cortex] scaffold copied: .context/, scripts/, mcp/, .githooks/, docs/");
983
+ console.log("[cortex] scaffold copied: .context/, .context/scripts/, .context/mcp/, .githooks/, docs/");
919
984
  console.log(`[cortex] Claude commands ready: /context-update (${helpers.claude.total} files)`);
920
985
  if (helpers.codex.changed) {
921
986
  console.log("[cortex] Codex workflow instructions added to AGENTS.md");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@danielblomma/cortex-mcp",
3
3
  "mcpName": "io.github.DanielBlomma/cortex",
4
- "version": "2.0.16",
4
+ "version": "2.0.19",
5
5
  "description": "Local, repo-scoped context platform for coding assistants. Semantic search, graph relationships, and architectural rule context.",
6
6
  "type": "module",
7
7
  "author": "Daniel Blomma",
@@ -49,6 +49,7 @@
49
49
  "docs/MCP_MARKETPLACE.md"
50
50
  ],
51
51
  "scripts": {
52
+ "pretest": "test -d scaffold/scripts/parsers/node_modules || npm --prefix scaffold/scripts/parsers install --no-fund --no-update-notifier --silent",
52
53
  "test": "node tests/context-regressions.test.mjs && node --test tests/ingest-units.test.mjs tests/javascript-parser.test.mjs tests/sql-parser.test.mjs tests/config-parser.test.mjs tests/resources-parser.test.mjs tests/vbnet-parser.test.mjs tests/cpp-parser.test.mjs tests/dashboard.test.mjs tests/init-config.test.mjs tests/init-agents.test.mjs tests/multi-level.test.mjs tests/no-legacy-paths.test.mjs tests/tree-sitter-error-reporting.test.mjs tests/tree-sitter-body-cap.test.mjs tests/tree-sitter-exported.test.mjs tests/tree-sitter-robustness.test.mjs",
53
54
  "release:sync-version": "node scripts/sync-release-version.mjs",
54
55
  "release:check-version-sync": "node scripts/sync-release-version.mjs --check",
@@ -1,11 +1,10 @@
1
1
  repo_id: cortex
2
2
  source_paths:
3
- - src
3
+ - scripts
4
+ - mcp/src
4
5
  - docs
5
- - design
6
6
  - .context/notes
7
7
  - .context/decisions
8
- - README.md
9
8
  truth_order:
10
9
  - ADR
11
10
  - RULE
@@ -6,12 +6,12 @@ if [[ -z "$REPO_ROOT" ]]; then
6
6
  exit 0
7
7
  fi
8
8
 
9
- CONTEXT_SCRIPT="$REPO_ROOT/scripts/context.sh"
9
+ CONTEXT_SCRIPT="$REPO_ROOT/.context/scripts/context.sh"
10
10
  if [[ ! -x "$CONTEXT_SCRIPT" ]]; then
11
11
  exit 0
12
12
  fi
13
13
 
14
- PARSER_MODULES_DIR="$REPO_ROOT/scripts/parsers/node_modules"
14
+ PARSER_MODULES_DIR="$REPO_ROOT/.context/scripts/parsers/node_modules"
15
15
 
16
16
  HOOK_DIR="$REPO_ROOT/.context/hooks"
17
17
  LOCK_DIR="$HOOK_DIR/update.lock"
@@ -91,12 +91,6 @@
91
91
  "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==",
92
92
  "license": "Apache-2.0"
93
93
  },
94
- "node_modules/@huggingface/transformers/node_modules/long": {
95
- "version": "5.3.2",
96
- "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
97
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
98
- "license": "Apache-2.0"
99
- },
100
94
  "node_modules/@huggingface/transformers/node_modules/onnxruntime-common": {
101
95
  "version": "1.24.3",
102
96
  "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
@@ -140,30 +134,6 @@
140
134
  "integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==",
141
135
  "license": "MIT"
142
136
  },
143
- "node_modules/@huggingface/transformers/node_modules/protobufjs": {
144
- "version": "7.5.5",
145
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz",
146
- "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==",
147
- "hasInstallScript": true,
148
- "license": "BSD-3-Clause",
149
- "dependencies": {
150
- "@protobufjs/aspromise": "^1.1.2",
151
- "@protobufjs/base64": "^1.1.2",
152
- "@protobufjs/codegen": "^2.0.4",
153
- "@protobufjs/eventemitter": "^1.1.0",
154
- "@protobufjs/fetch": "^1.1.0",
155
- "@protobufjs/float": "^1.0.2",
156
- "@protobufjs/inquire": "^1.1.0",
157
- "@protobufjs/path": "^1.1.2",
158
- "@protobufjs/pool": "^1.1.0",
159
- "@protobufjs/utf8": "^1.1.0",
160
- "@types/node": ">=13.7.0",
161
- "long": "^5.0.0"
162
- },
163
- "engines": {
164
- "node": ">=12.0.0"
165
- }
166
- },
167
137
  "node_modules/@huggingface/transformers/node_modules/sharp": {
168
138
  "version": "0.34.5",
169
139
  "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
@@ -766,9 +736,9 @@
766
736
  "license": "BSD-3-Clause"
767
737
  },
768
738
  "node_modules/@protobufjs/codegen": {
769
- "version": "2.0.4",
770
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
771
- "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
739
+ "version": "2.0.5",
740
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
741
+ "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
772
742
  "license": "BSD-3-Clause"
773
743
  },
774
744
  "node_modules/@protobufjs/eventemitter": {
@@ -778,13 +748,12 @@
778
748
  "license": "BSD-3-Clause"
779
749
  },
780
750
  "node_modules/@protobufjs/fetch": {
781
- "version": "1.1.0",
782
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
783
- "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
751
+ "version": "1.1.1",
752
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
753
+ "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
784
754
  "license": "BSD-3-Clause",
785
755
  "dependencies": {
786
- "@protobufjs/aspromise": "^1.1.1",
787
- "@protobufjs/inquire": "^1.1.0"
756
+ "@protobufjs/aspromise": "^1.1.1"
788
757
  }
789
758
  },
790
759
  "node_modules/@protobufjs/float": {
@@ -794,9 +763,9 @@
794
763
  "license": "BSD-3-Clause"
795
764
  },
796
765
  "node_modules/@protobufjs/inquire": {
797
- "version": "1.1.0",
798
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
799
- "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
766
+ "version": "1.1.2",
767
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
768
+ "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
800
769
  "license": "BSD-3-Clause"
801
770
  },
802
771
  "node_modules/@protobufjs/path": {
@@ -812,9 +781,9 @@
812
781
  "license": "BSD-3-Clause"
813
782
  },
814
783
  "node_modules/@protobufjs/utf8": {
815
- "version": "1.1.0",
816
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
817
- "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
784
+ "version": "1.1.1",
785
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
786
+ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
818
787
  "license": "BSD-3-Clause"
819
788
  },
820
789
  "node_modules/@tsconfig/node10": {
@@ -1044,9 +1013,9 @@
1044
1013
  "license": "MIT"
1045
1014
  },
1046
1015
  "node_modules/brace-expansion": {
1047
- "version": "5.0.5",
1048
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
1049
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
1016
+ "version": "5.0.6",
1017
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
1018
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
1050
1019
  "license": "MIT",
1051
1020
  "dependencies": {
1052
1021
  "balanced-match": "^4.0.2"
@@ -1525,12 +1494,12 @@
1525
1494
  }
1526
1495
  },
1527
1496
  "node_modules/express-rate-limit": {
1528
- "version": "8.3.1",
1529
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
1530
- "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
1497
+ "version": "8.5.1",
1498
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz",
1499
+ "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==",
1531
1500
  "license": "MIT",
1532
1501
  "dependencies": {
1533
- "ip-address": "10.1.0"
1502
+ "ip-address": "^10.2.0"
1534
1503
  },
1535
1504
  "engines": {
1536
1505
  "node": ">= 16"
@@ -1574,9 +1543,9 @@
1574
1543
  "license": "MIT"
1575
1544
  },
1576
1545
  "node_modules/fast-uri": {
1577
- "version": "3.1.0",
1578
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
1579
- "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
1546
+ "version": "3.1.2",
1547
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
1548
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
1580
1549
  "funding": [
1581
1550
  {
1582
1551
  "type": "github",
@@ -1791,9 +1760,9 @@
1791
1760
  }
1792
1761
  },
1793
1762
  "node_modules/hono": {
1794
- "version": "4.12.14",
1795
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
1796
- "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
1763
+ "version": "4.12.18",
1764
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
1765
+ "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
1797
1766
  "license": "MIT",
1798
1767
  "engines": {
1799
1768
  "node": ">=16.9.0"
@@ -1848,9 +1817,9 @@
1848
1817
  "license": "ISC"
1849
1818
  },
1850
1819
  "node_modules/ip-address": {
1851
- "version": "10.1.0",
1852
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
1853
- "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
1820
+ "version": "10.2.0",
1821
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
1822
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
1854
1823
  "license": "MIT",
1855
1824
  "engines": {
1856
1825
  "node": ">= 12"
@@ -1937,6 +1906,12 @@
1937
1906
  "graceful-fs": "^4.1.6"
1938
1907
  }
1939
1908
  },
1909
+ "node_modules/long": {
1910
+ "version": "5.3.2",
1911
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
1912
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
1913
+ "license": "Apache-2.0"
1914
+ },
1940
1915
  "node_modules/make-error": {
1941
1916
  "version": "1.3.6",
1942
1917
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -2152,6 +2127,30 @@
2152
2127
  "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
2153
2128
  "license": "MIT"
2154
2129
  },
2130
+ "node_modules/protobufjs": {
2131
+ "version": "7.5.9",
2132
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.9.tgz",
2133
+ "integrity": "sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA==",
2134
+ "hasInstallScript": true,
2135
+ "license": "BSD-3-Clause",
2136
+ "dependencies": {
2137
+ "@protobufjs/aspromise": "^1.1.2",
2138
+ "@protobufjs/base64": "^1.1.2",
2139
+ "@protobufjs/codegen": "^2.0.5",
2140
+ "@protobufjs/eventemitter": "^1.1.0",
2141
+ "@protobufjs/fetch": "^1.1.1",
2142
+ "@protobufjs/float": "^1.0.2",
2143
+ "@protobufjs/inquire": "^1.1.2",
2144
+ "@protobufjs/path": "^1.1.2",
2145
+ "@protobufjs/pool": "^1.1.0",
2146
+ "@protobufjs/utf8": "^1.1.1",
2147
+ "@types/node": ">=13.7.0",
2148
+ "long": "^5.0.0"
2149
+ },
2150
+ "engines": {
2151
+ "node": ">=12.0.0"
2152
+ }
2153
+ },
2155
2154
  "node_modules/proxy-addr": {
2156
2155
  "version": "2.0.7",
2157
2156
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "overrides": {
25
25
  "cmake-js": "^8.0.0",
26
- "express-rate-limit": "^8.3.1",
27
- "hono": "^4.12.14",
26
+ "express-rate-limit": "^8.5.1",
27
+ "hono": "^4.12.18",
28
28
  "@hono/node-server": "^1.19.13",
29
29
  "tar": "^7.5.11"
30
30
  },
@@ -8,8 +8,8 @@ import {
8
8
  type StageStatus,
9
9
  type WorkflowDefinition,
10
10
  } from "../core/workflow/index.js";
11
- import { DEFAULT_WORKFLOWS } from "../core/workflow/default-workflows.js";
12
- import { isEnterpriseProject } from "../hooks/shared.js";
11
+ import { resolveWorkflowDefinition } from "../core/workflow/resolution.js";
12
+ import { isEnforcedMode, isEnterpriseProject } from "../hooks/shared.js";
13
13
 
14
14
  /**
15
15
  * `cortex stage` CLI surface. Each subcommand is a thin shell wrapper
@@ -93,18 +93,6 @@ function projectRoot(): string {
93
93
  return process.env.CORTEX_PROJECT_ROOT?.trim() || process.cwd();
94
94
  }
95
95
 
96
- function resolveWorkflow(workflowId: string): WorkflowDefinition {
97
- const wf = DEFAULT_WORKFLOWS[workflowId];
98
- if (!wf) {
99
- throw new Error(
100
- `Unknown workflow_id: ${workflowId}. Available: ${
101
- Object.keys(DEFAULT_WORKFLOWS).join(", ") || "<none>"
102
- }`,
103
- );
104
- }
105
- return wf;
106
- }
107
-
108
96
  type Flags = Record<string, string | boolean>;
109
97
 
110
98
  function parseFlags(args: string[]): { flags: Flags; rest: string[] } {
@@ -153,7 +141,11 @@ async function runStart(args: string[]): Promise<void> {
153
141
  const workflowId =
154
142
  typeof flags.workflow === "string" ? flags.workflow : "secure-build";
155
143
 
156
- const workflow = resolveWorkflow(workflowId);
144
+ const resolved = resolveWorkflowDefinition(workflowId, {
145
+ emitBundledFallbackWarning: true,
146
+ bundledFallbackPolicy: isEnforcedMode(projectRoot()) ? "block" : "warn",
147
+ });
148
+ const workflow = resolved.workflow;
157
149
  const state = createRun({
158
150
  cwd: projectRoot(),
159
151
  taskId,
@@ -165,7 +157,12 @@ async function runStart(args: string[]): Promise<void> {
165
157
  taskId,
166
158
  workflow,
167
159
  });
168
- emitJson({ state, envelope });
160
+ emitJson({
161
+ state,
162
+ envelope,
163
+ workflow_source: resolved.source,
164
+ warnings: resolved.warnings,
165
+ });
169
166
  }
170
167
 
171
168
  async function runStatus(args: string[]): Promise<void> {
@@ -186,7 +183,7 @@ async function runEnvelope(args: string[]): Promise<void> {
186
183
  `No run state for task ${taskId}. Start one with 'cortex stage start'.`,
187
184
  );
188
185
  }
189
- const workflow = resolveWorkflow(state.workflow_id);
186
+ const workflow = resolveWorkflowDefinition(state.workflow_id).workflow;
190
187
  const envelope = composeStageEnvelope({
191
188
  cwd: projectRoot(),
192
189
  taskId,
@@ -246,7 +243,7 @@ async function runAdvance(args: string[]): Promise<void> {
246
243
  `No run state for task ${taskId}. Start one with 'cortex stage start'.`,
247
244
  );
248
245
  }
249
- const workflow = resolveWorkflow(state.workflow_id);
246
+ const workflow = resolveWorkflowDefinition(state.workflow_id).workflow;
250
247
  const stage = workflow.stages.find((s) => s.name === stageName);
251
248
  if (!stage) {
252
249
  throw new Error(
@@ -100,6 +100,21 @@ type ReviewStatusPayload = {
100
100
  reviewedFiles: ReviewedFileSnapshot[] | null;
101
101
  };
102
102
 
103
+ function formatReviewEvidenceSource(
104
+ source: ReviewStatusPayload["source"] | "none",
105
+ ): string {
106
+ switch (source) {
107
+ case "workflow-state":
108
+ return "workflow state";
109
+ case "workflow-artifact":
110
+ return "workflow artifact";
111
+ case "legacy-review-status":
112
+ return "legacy file";
113
+ default:
114
+ return "none";
115
+ }
116
+ }
117
+
103
118
  type ReviewedFileSnapshot = {
104
119
  path: string;
105
120
  exists: boolean;
@@ -474,7 +489,9 @@ registerValidator({
474
489
  pass: false,
475
490
  severity: "warning",
476
491
  message: `${sourceLabel} at ${reviewStatus.timestamp} is stale for current changes`,
477
- detail: `Source: ${reviewStatus.source}. ${freshness.detail ?? "Current changes no longer match the reviewed code."}`,
492
+ detail:
493
+ `Source: ${formatReviewEvidenceSource(reviewStatus.source)}. ` +
494
+ `${freshness.detail ?? "Current changes no longer match the reviewed code."}`,
478
495
  };
479
496
  }
480
497
 
@@ -485,7 +502,7 @@ registerValidator({
485
502
  message: reviewStatus.reviewed
486
503
  ? `Enterprise review completed by ${reviewStatus.reviewer} at ${reviewStatus.timestamp}`
487
504
  : `Enterprise review recorded at ${reviewStatus.timestamp} but did not pass`,
488
- detail: `Source: ${reviewStatus.source}`,
505
+ detail: `Source: ${formatReviewEvidenceSource(reviewStatus.source)}`,
489
506
  };
490
507
  }
491
508
 
@@ -496,7 +513,9 @@ registerValidator({
496
513
  message: reviewStatus.reviewed
497
514
  ? `Code review completed by ${reviewStatus.reviewer} at ${reviewStatus.timestamp}`
498
515
  : "Code review recorded but not approved",
499
- detail: reviewStatus.reviewed ? undefined : `Source: ${reviewStatus.source}`,
516
+ detail: reviewStatus.reviewed
517
+ ? undefined
518
+ : `Source: ${formatReviewEvidenceSource(reviewStatus.source)}`,
500
519
  };
501
520
  }
502
521
 
@@ -504,7 +523,8 @@ registerValidator({
504
523
  pass: false,
505
524
  severity: "warning",
506
525
  message: "No code review recorded for current changes",
507
- detail: "Run /review or context.review, or ensure your CI writes .context/review-status.json.",
526
+ detail:
527
+ "Source: none. Run /review or context.review, or ensure your CI writes .context/review-status.json.",
508
528
  };
509
529
  },
510
530
  });
@@ -8,3 +8,4 @@ export * from "./capabilities.js";
8
8
  export * from "./enforcement.js";
9
9
  export * from "./synced-registry.js";
10
10
  export * from "./synced-capability-registry.js";
11
+ export * from "./resolution.js";