@danielblomma/cortex-mcp 2.0.16 → 2.0.18
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.
- package/README.md +5 -3
- package/package.json +1 -1
- package/scaffold/mcp/package-lock.json +59 -60
- package/scaffold/mcp/package.json +2 -2
- package/scaffold/mcp/src/cli/stage.ts +15 -18
- package/scaffold/mcp/src/core/validators/builtins.ts +24 -4
- package/scaffold/mcp/src/core/workflow/index.ts +1 -0
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +15 -25
- package/scaffold/mcp/src/core/workflow/resolution.ts +113 -0
- package/scaffold/mcp/src/enterprise/index.ts +16 -1
- package/scaffold/mcp/src/enterprise/reviews/policy-selection.ts +65 -0
- package/scaffold/mcp/src/enterprise/reviews/push.ts +55 -4
- package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +305 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +40 -47
- package/scaffold/mcp/src/enterprise/tools/harness.ts +2 -0
- package/scaffold/mcp/src/enterprise/workflow/state.ts +21 -0
- package/scaffold/mcp/tests/enterprise-review-trust.test.mjs +318 -0
- package/scaffold/mcp/tests/fixtures/org-skillz-contract.json +59 -0
- package/scaffold/mcp/tests/fixtures/review-trust-contract.json +111 -0
- package/scaffold/mcp/tests/review-policy-selection.test.mjs +85 -0
- package/scaffold/mcp/tests/review-trust-contract.test.mjs +167 -0
- package/scaffold/mcp/tests/review-validator-source.test.mjs +52 -0
- package/scaffold/mcp/tests/skill-sync-checker.test.mjs +277 -0
- package/scaffold/mcp/tests/workflow-cli.test.mjs +102 -1
- package/scaffold/mcp/tests/workflow-mcp-tools.test.mjs +22 -0
- package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +56 -0
- package/scaffold/scripts/parsers/node_modules/.package-lock.json +0 -1
package/README.md
CHANGED
|
@@ -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
|
|
351
|
+
Required npm configuration:
|
|
352
352
|
|
|
353
|
-
-
|
|
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/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.
|
|
4
|
+
"version": "2.0.18",
|
|
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",
|
|
@@ -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.
|
|
770
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.
|
|
771
|
-
"integrity": "sha512-
|
|
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.
|
|
782
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.
|
|
783
|
-
"integrity": "sha512-
|
|
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.
|
|
798
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.
|
|
799
|
-
"integrity": "sha512-
|
|
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.
|
|
816
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.
|
|
817
|
-
"integrity": "sha512-
|
|
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.
|
|
1048
|
-
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.
|
|
1049
|
-
"integrity": "sha512-
|
|
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.
|
|
1529
|
-
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.
|
|
1530
|
-
"integrity": "sha512-
|
|
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.
|
|
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.
|
|
1578
|
-
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.
|
|
1579
|
-
"integrity": "sha512-
|
|
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.
|
|
1795
|
-
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.
|
|
1796
|
-
"integrity": "sha512-
|
|
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.
|
|
1852
|
-
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.
|
|
1853
|
-
"integrity": "sha512
|
|
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",
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
type StageStatus,
|
|
9
9
|
type WorkflowDefinition,
|
|
10
10
|
} from "../core/workflow/index.js";
|
|
11
|
-
import {
|
|
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
|
|
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({
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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:
|
|
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
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { advanceStage, createRun, getRunState } from "./run-lifecycle.js";
|
|
3
3
|
import { composeStageEnvelope } from "./envelope.js";
|
|
4
|
-
import {
|
|
5
|
-
import { loadSyncedWorkflows } from "./synced-registry.js";
|
|
4
|
+
import { resolveWorkflowDefinition } from "./resolution.js";
|
|
6
5
|
import {
|
|
7
6
|
stageOverrideSchema,
|
|
8
7
|
stageStatusSchema,
|
|
@@ -88,33 +87,18 @@ export function resolveProjectRoot(): string {
|
|
|
88
87
|
export type WorkflowToolContext = {
|
|
89
88
|
cwd: string;
|
|
90
89
|
workflows?: Record<string, WorkflowDefinition>;
|
|
90
|
+
bundledFallbackPolicy?: "allow" | "warn" | "block";
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
function resolveWorkflow(
|
|
94
|
-
workflowId: string,
|
|
95
|
-
registry: Record<string, WorkflowDefinition> | undefined,
|
|
96
|
-
): WorkflowDefinition {
|
|
97
|
-
// When the caller passes an explicit registry, it wins outright (used
|
|
98
|
-
// by tests). Otherwise we merge bundled defaults with the org-authored
|
|
99
|
-
// workflows the daemon has synced into ~/.cortex/workflows.local.json,
|
|
100
|
-
// with the synced ones taking precedence on workflow_id collisions so
|
|
101
|
-
// org overrides actually override.
|
|
102
|
-
const workflows =
|
|
103
|
-
registry ?? { ...DEFAULT_WORKFLOWS, ...loadSyncedWorkflows() };
|
|
104
|
-
const workflow = workflows[workflowId];
|
|
105
|
-
if (!workflow) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Unknown workflow_id: ${workflowId}. Available: ${Object.keys(workflows).join(", ") || "<none>"}`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
return workflow;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
93
|
export function runWorkflowStart(
|
|
114
94
|
input: WorkflowStartInputT,
|
|
115
95
|
ctx: WorkflowToolContext,
|
|
116
96
|
) {
|
|
117
|
-
const
|
|
97
|
+
const resolved = resolveWorkflowDefinition(input.workflow_id, {
|
|
98
|
+
registry: ctx.workflows,
|
|
99
|
+
bundledFallbackPolicy: ctx.bundledFallbackPolicy,
|
|
100
|
+
});
|
|
101
|
+
const workflow = resolved.workflow;
|
|
118
102
|
const state = createRun({
|
|
119
103
|
cwd: ctx.cwd,
|
|
120
104
|
taskId: input.task_id,
|
|
@@ -129,6 +113,8 @@ export function runWorkflowStart(
|
|
|
129
113
|
return {
|
|
130
114
|
state,
|
|
131
115
|
envelope,
|
|
116
|
+
workflow_source: resolved.source,
|
|
117
|
+
warnings: resolved.warnings,
|
|
132
118
|
};
|
|
133
119
|
}
|
|
134
120
|
|
|
@@ -142,7 +128,9 @@ export function runWorkflowAdvance(
|
|
|
142
128
|
`No run state found for task ${input.task_id}. Call cortex.workflow.start first.`,
|
|
143
129
|
);
|
|
144
130
|
}
|
|
145
|
-
const workflow =
|
|
131
|
+
const workflow = resolveWorkflowDefinition(state.workflow_id, {
|
|
132
|
+
registry: ctx.workflows,
|
|
133
|
+
}).workflow;
|
|
146
134
|
const stage = workflow.stages.find((s) => s.name === input.stage);
|
|
147
135
|
if (!stage) {
|
|
148
136
|
throw new Error(`Stage ${input.stage} is not defined in workflow ${workflow.id}`);
|
|
@@ -220,7 +208,9 @@ export function runWorkflowEnvelope(
|
|
|
220
208
|
`No run state found for task ${input.task_id}. Call cortex.workflow.start first.`,
|
|
221
209
|
);
|
|
222
210
|
}
|
|
223
|
-
const workflow =
|
|
211
|
+
const workflow = resolveWorkflowDefinition(state.workflow_id, {
|
|
212
|
+
registry: ctx.workflows,
|
|
213
|
+
}).workflow;
|
|
224
214
|
const envelope = composeStageEnvelope({
|
|
225
215
|
cwd: ctx.cwd,
|
|
226
216
|
taskId: input.task_id,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { DEFAULT_WORKFLOWS } from "./default-workflows.js";
|
|
2
|
+
import { loadSyncedWorkflows } from "./synced-registry.js";
|
|
3
|
+
import type { WorkflowDefinition } from "./schemas.js";
|
|
4
|
+
|
|
5
|
+
export type WorkflowSource = "bundled" | "synced" | "injected";
|
|
6
|
+
|
|
7
|
+
export type WorkflowResolutionWarning = {
|
|
8
|
+
code: "bundled-workflow-fallback";
|
|
9
|
+
workflow_id: string;
|
|
10
|
+
message: string;
|
|
11
|
+
bundled_available: boolean;
|
|
12
|
+
synced_available: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type WorkflowResolution = {
|
|
16
|
+
workflow: WorkflowDefinition;
|
|
17
|
+
source: WorkflowSource;
|
|
18
|
+
warnings: WorkflowResolutionWarning[];
|
|
19
|
+
bundled_ids: string[];
|
|
20
|
+
synced_ids: string[];
|
|
21
|
+
available_ids: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type ResolveWorkflowOptions = {
|
|
25
|
+
registry?: Record<string, WorkflowDefinition>;
|
|
26
|
+
syncedDir?: string;
|
|
27
|
+
emitBundledFallbackWarning?: boolean;
|
|
28
|
+
bundledFallbackPolicy?: "allow" | "warn" | "block";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function sortIds(ids: Iterable<string>): string[] {
|
|
32
|
+
return [...ids].sort((left, right) => left.localeCompare(right));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatIds(ids: string[]): string {
|
|
36
|
+
return ids.length > 0 ? ids.join(", ") : "<none>";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveWorkflowDefinition(
|
|
40
|
+
workflowId: string,
|
|
41
|
+
options: ResolveWorkflowOptions = {},
|
|
42
|
+
): WorkflowResolution {
|
|
43
|
+
if (options.registry) {
|
|
44
|
+
const availableIds = sortIds(Object.keys(options.registry));
|
|
45
|
+
const workflow = options.registry[workflowId];
|
|
46
|
+
if (!workflow) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Unknown workflow_id: ${workflowId}. Available injected: ${formatIds(availableIds)}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
workflow,
|
|
53
|
+
source: "injected",
|
|
54
|
+
warnings: [],
|
|
55
|
+
bundled_ids: [],
|
|
56
|
+
synced_ids: [],
|
|
57
|
+
available_ids: availableIds,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const bundled = DEFAULT_WORKFLOWS;
|
|
62
|
+
const synced = loadSyncedWorkflows(options.syncedDir);
|
|
63
|
+
const bundledIds = sortIds(Object.keys(bundled));
|
|
64
|
+
const syncedIds = sortIds(Object.keys(synced));
|
|
65
|
+
const merged = { ...bundled, ...synced };
|
|
66
|
+
const availableIds = sortIds(Object.keys(merged));
|
|
67
|
+
const workflow = merged[workflowId];
|
|
68
|
+
|
|
69
|
+
if (!workflow) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Unknown workflow_id: ${workflowId}. Available bundled: ${formatIds(bundledIds)}. ` +
|
|
72
|
+
`Available synced: ${formatIds(syncedIds)}. Available all: ${formatIds(availableIds)}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const source: WorkflowSource = Object.prototype.hasOwnProperty.call(synced, workflowId)
|
|
77
|
+
? "synced"
|
|
78
|
+
: "bundled";
|
|
79
|
+
const warnings: WorkflowResolutionWarning[] = [];
|
|
80
|
+
const fallbackPolicy = options.bundledFallbackPolicy ?? "allow";
|
|
81
|
+
|
|
82
|
+
if (source === "bundled" && fallbackPolicy === "block") {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Workflow "${workflowId}" is only available from the bundled registry, ` +
|
|
85
|
+
"but enforced govern mode requires a synced org workflow. " +
|
|
86
|
+
`Available synced: ${formatIds(syncedIds)}.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
source === "bundled" &&
|
|
92
|
+
(options.emitBundledFallbackWarning || fallbackPolicy === "warn")
|
|
93
|
+
) {
|
|
94
|
+
warnings.push({
|
|
95
|
+
code: "bundled-workflow-fallback",
|
|
96
|
+
workflow_id: workflowId,
|
|
97
|
+
bundled_available: true,
|
|
98
|
+
synced_available: syncedIds.length > 0,
|
|
99
|
+
message:
|
|
100
|
+
`Workflow "${workflowId}" was resolved from the bundled registry because ` +
|
|
101
|
+
"no synced org workflow with that id exists in the local cache.",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
workflow,
|
|
107
|
+
source,
|
|
108
|
+
warnings,
|
|
109
|
+
bundled_ids: bundledIds,
|
|
110
|
+
synced_ids: syncedIds,
|
|
111
|
+
available_ids: availableIds,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -17,7 +17,8 @@ import { registerEnterpriseTools } from "./tools/enterprise.js";
|
|
|
17
17
|
import { registerHarnessTools } from "./tools/harness.js";
|
|
18
18
|
import { pushViolations, setViolationPushContext } from "./violations/push.js";
|
|
19
19
|
import { pushReviewResults, setReviewPushContext } from "./reviews/push.js";
|
|
20
|
-
import { setWorkflowPushContext } from "./workflow/push.js";
|
|
20
|
+
import { pushWorkflowSnapshot, setWorkflowPushContext } from "./workflow/push.js";
|
|
21
|
+
import { hasWorkflowState, loadWorkflowState } from "./workflow/state.js";
|
|
21
22
|
|
|
22
23
|
const require = createRequire(import.meta.url);
|
|
23
24
|
const pkg = require("../package.json") as { version: string };
|
|
@@ -32,6 +33,7 @@ let activeAuditWriter: AuditWriter | null = null;
|
|
|
32
33
|
let activeInstanceId: string | null = null;
|
|
33
34
|
let activeSessionId: string | null = null;
|
|
34
35
|
let activeRepo: string | null = null;
|
|
36
|
+
let activeContextDir: string | null = null;
|
|
35
37
|
|
|
36
38
|
async function flushComplianceQueues(
|
|
37
39
|
config: EnterpriseConfig,
|
|
@@ -64,6 +66,15 @@ async function flushComplianceQueues(
|
|
|
64
66
|
if (!result.success) {
|
|
65
67
|
process.stderr.write(`[cortex-enterprise] ${reason} reviews push failed: ${result.error}\n`);
|
|
66
68
|
}
|
|
69
|
+
if (result.attempted && activeContextDir && hasWorkflowState(activeContextDir)) {
|
|
70
|
+
const workflowState = loadWorkflowState(activeContextDir);
|
|
71
|
+
const workflowResult = await pushWorkflowSnapshot(baseUrl, apiKey, workflowState);
|
|
72
|
+
if (!workflowResult.success) {
|
|
73
|
+
process.stderr.write(
|
|
74
|
+
`[cortex-enterprise] ${reason} workflow snapshot refresh after review push failed: ${workflowResult.error}\n`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
67
78
|
} catch (err) {
|
|
68
79
|
process.stderr.write(`[cortex-enterprise] ${reason} reviews push error: ${err}\n`);
|
|
69
80
|
}
|
|
@@ -113,6 +124,7 @@ export function shutdown(): void {
|
|
|
113
124
|
activeInstanceId = null;
|
|
114
125
|
activeSessionId = null;
|
|
115
126
|
activeRepo = null;
|
|
127
|
+
activeContextDir = null;
|
|
116
128
|
setAuditPushContext({});
|
|
117
129
|
setViolationPushContext({});
|
|
118
130
|
setReviewPushContext({});
|
|
@@ -248,6 +260,7 @@ export async function onSessionEvent(event: SessionEvent): Promise<void> {
|
|
|
248
260
|
export async function register(server: McpServer): Promise<void> {
|
|
249
261
|
const projectRoot = process.env.CORTEX_PROJECT_ROOT?.trim() || process.cwd();
|
|
250
262
|
const contextDir = path.join(projectRoot, ".context");
|
|
263
|
+
activeContextDir = contextDir;
|
|
251
264
|
|
|
252
265
|
const config = loadEnterpriseConfig(contextDir);
|
|
253
266
|
const activation = resolveEnterpriseActivation(config);
|
|
@@ -295,6 +308,8 @@ export async function register(server: McpServer): Promise<void> {
|
|
|
295
308
|
repo: activeRepo ?? undefined,
|
|
296
309
|
instance_id: activeInstanceId ?? undefined,
|
|
297
310
|
session_id: activeSessionId ?? undefined,
|
|
311
|
+
context_dir: contextDir,
|
|
312
|
+
project_root: projectRoot,
|
|
298
313
|
});
|
|
299
314
|
setWorkflowPushContext({
|
|
300
315
|
repo: activeRepo ?? undefined,
|