@codyswann/lisa 2.290.0 → 2.292.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.
- package/all/merge/.claude/settings.json +1 -1
- package/cdk/merge/.claude/settings.json +1 -1
- package/dist/core/config-field-validation.d.ts +19 -0
- package/dist/core/config-field-validation.d.ts.map +1 -0
- package/dist/core/config-field-validation.js +69 -0
- package/dist/core/config-field-validation.js.map +1 -0
- package/dist/core/deploy-status-sync.d.ts +88 -0
- package/dist/core/deploy-status-sync.d.ts.map +1 -0
- package/dist/core/deploy-status-sync.js +301 -0
- package/dist/core/deploy-status-sync.js.map +1 -0
- package/dist/core/project-config.d.ts +3 -0
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +4 -40
- package/dist/core/project-config.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +21 -13
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +299 -35
- package/dist/sync/registry.d.ts.map +1 -1
- package/dist/sync/registry.js +3 -6
- package/dist/sync/registry.js.map +1 -1
- package/expo/merge/.claude/settings.json +1 -1
- package/harper-fabric/merge/.claude/settings.json +1 -1
- package/nestjs/merge/.claude/settings.json +1 -1
- package/package.json +1 -1
- package/phaser/merge/.claude/settings.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-safety-net-rules/SKILL.md +41 -5
- package/plugins/lisa/hooks/parity-safety-net.sh +299 -35
- package/plugins/lisa/rules/eager/config-resolution.md +8 -0
- package/plugins/lisa/rules/reference/config-resolution.md +47 -0
- package/plugins/lisa/skills/lisa-parity-safety-net-rules/SKILL.md +41 -5
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +299 -35
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-parity-safety-net-rules/SKILL.md +41 -5
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +299 -35
- package/plugins/lisa-copilot/rules/eager/config-resolution.md +8 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +47 -0
- package/plugins/lisa-copilot/skills/lisa-parity-safety-net-rules/SKILL.md +41 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +299 -35
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +47 -0
- package/plugins/lisa-cursor/rules/config-resolution.mdc +8 -0
- package/plugins/lisa-cursor/skills/lisa-parity-safety-net-rules/SKILL.md +41 -5
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +299 -35
- package/plugins/src/base/rules/eager/config-resolution.md +8 -0
- package/plugins/src/base/rules/reference/config-resolution.md +47 -0
- package/plugins/src/base/skills/lisa-parity-safety-net-rules/SKILL.md +41 -5
- package/rails/merge/.claude/settings.json +1 -1
- package/scripts/install-claude-plugins.sh +9 -6
- package/typescript/merge/.claude/settings.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
},
|
|
6
6
|
"enabledPlugins": {
|
|
7
7
|
"lisa@lisa": true,
|
|
8
|
-
"safety-net@cc-marketplace":
|
|
8
|
+
"safety-net@cc-marketplace": false,
|
|
9
9
|
"code-simplifier@claude-plugins-official": true,
|
|
10
10
|
"code-review@claude-plugins-official": true,
|
|
11
11
|
"coderabbit@claude-plugins-official": true,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
},
|
|
6
6
|
"enabledPlugins": {
|
|
7
7
|
"typescript-lsp@claude-plugins-official": true,
|
|
8
|
-
"safety-net@cc-marketplace":
|
|
8
|
+
"safety-net@cc-marketplace": false,
|
|
9
9
|
"code-simplifier@claude-plugins-official": true,
|
|
10
10
|
"code-review@claude-plugins-official": true,
|
|
11
11
|
"coderabbit@claude-plugins-official": true,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate an optional non-empty trimmed string field.
|
|
3
|
+
* @param value - Untrusted field value
|
|
4
|
+
* @param source - Config source shown in errors
|
|
5
|
+
* @param field - Dotted field name
|
|
6
|
+
* @returns Valid string or undefined
|
|
7
|
+
*/
|
|
8
|
+
export declare function optionalString(value: unknown, source: string, field: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Validate a configurable destination as a safe, relative, non-traversing
|
|
11
|
+
* Markdown path. Shared by every path-typed config field; the `field` label is
|
|
12
|
+
* interpolated into each diagnostic so callers get a field-specific message.
|
|
13
|
+
* @param value - Raw configured path
|
|
14
|
+
* @param source - Config source for errors
|
|
15
|
+
* @param field - Config field name used in error messages
|
|
16
|
+
* @returns Validated project-relative path
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateSafeRelativeMarkdownPath(value: unknown, source: string, field: string): string;
|
|
19
|
+
//# sourceMappingURL=config-field-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-field-validation.d.ts","sourceRoot":"","sources":["../../src/core/config-field-validation.ts"],"names":[],"mappings":"AAuBA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,SAAS,CAapB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,MAAM,CA6BR"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared validation for `.lisa.config.json` field values.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `project-config.ts` so path-typed destinations
|
|
5
|
+
* (`projectRulesFile`, `learnings.file`, …) and string-typed section fields
|
|
6
|
+
* (`deployStatusSync.*`) funnel through the same hardened checks without
|
|
7
|
+
* growing the config module.
|
|
8
|
+
* @module core/config-field-validation
|
|
9
|
+
*/
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
/**
|
|
12
|
+
* Whether a path contains an ASCII control character.
|
|
13
|
+
* @param value - Configured path
|
|
14
|
+
* @returns True when any control character is present
|
|
15
|
+
*/
|
|
16
|
+
function containsControlCharacter(value) {
|
|
17
|
+
return Array.from(value).some(character => {
|
|
18
|
+
const code = character.charCodeAt(0);
|
|
19
|
+
return code <= 31 || code === 127;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate an optional non-empty trimmed string field.
|
|
24
|
+
* @param value - Untrusted field value
|
|
25
|
+
* @param source - Config source shown in errors
|
|
26
|
+
* @param field - Dotted field name
|
|
27
|
+
* @returns Valid string or undefined
|
|
28
|
+
*/
|
|
29
|
+
export function optionalString(value, source, field) {
|
|
30
|
+
if (value === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (typeof value !== "string" ||
|
|
33
|
+
value.trim() !== value ||
|
|
34
|
+
value.length === 0 ||
|
|
35
|
+
containsControlCharacter(value)) {
|
|
36
|
+
throw new Error(`Invalid ${field} in ${source}: expected a non-empty string`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validate a configurable destination as a safe, relative, non-traversing
|
|
42
|
+
* Markdown path. Shared by every path-typed config field; the `field` label is
|
|
43
|
+
* interpolated into each diagnostic so callers get a field-specific message.
|
|
44
|
+
* @param value - Raw configured path
|
|
45
|
+
* @param source - Config source for errors
|
|
46
|
+
* @param field - Config field name used in error messages
|
|
47
|
+
* @returns Validated project-relative path
|
|
48
|
+
*/
|
|
49
|
+
export function validateSafeRelativeMarkdownPath(value, source, field) {
|
|
50
|
+
if (typeof value !== "string" ||
|
|
51
|
+
value.length === 0 ||
|
|
52
|
+
value.trim() !== value ||
|
|
53
|
+
value.includes("\\") ||
|
|
54
|
+
containsControlCharacter(value) ||
|
|
55
|
+
/^[a-z]:/iu.test(value) ||
|
|
56
|
+
path.posix.isAbsolute(value) ||
|
|
57
|
+
path.win32.isAbsolute(value)) {
|
|
58
|
+
throw new Error(`Invalid ${field} in ${source}: expected a safe relative POSIX path`);
|
|
59
|
+
}
|
|
60
|
+
const segments = value.split("/");
|
|
61
|
+
if (segments.some(segment => segment === "" || segment === "." || segment === "..")) {
|
|
62
|
+
throw new Error(`Invalid ${field} in ${source}: path traversal is not allowed`);
|
|
63
|
+
}
|
|
64
|
+
if (path.posix.extname(value).toLowerCase() !== ".md") {
|
|
65
|
+
throw new Error(`Invalid ${field} in ${source}: expected a Markdown file`);
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=config-field-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-field-validation.js","sourceRoot":"","sources":["../../src/core/config-field-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,KAAa;IAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;QACxC,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,IAAI,IAAI,EAAE,IAAI,IAAI,KAAK,GAAG,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAc,EACd,MAAc,EACd,KAAa;IAEb,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK;QACtB,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,wBAAwB,CAAC,KAAK,CAAC,EAC/B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,WAAW,KAAK,OAAO,MAAM,+BAA+B,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gCAAgC,CAC9C,KAAc,EACd,MAAc,EACd,KAAa;IAEb,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK;QACtB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,wBAAwB,CAAC,KAAK,CAAC;QAC/B,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAC5B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,WAAW,KAAK,OAAO,MAAM,uCAAuC,CACrE,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,IACE,QAAQ,CAAC,IAAI,CACX,OAAO,CAAC,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,CACjE,EACD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,WAAW,KAAK,OAAO,MAAM,iCAAiC,CAC/D,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,MAAM,4BAA4B,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy-status-sync ladder resolution and config schema (DSS-1).
|
|
3
|
+
*
|
|
4
|
+
* Pure, fs-free helpers that turn a parsed `.lisa.config.json` value into the
|
|
5
|
+
* ordered environment ladder (env → deploy branch → tracker done status) the
|
|
6
|
+
* deploy-status-sync flows walk, plus the validator for the optional
|
|
7
|
+
* `deployStatusSync` section. The default done vocabularies live here — and
|
|
8
|
+
* are consumed by `src/sync/registry.ts` — so the ladder resolver and the
|
|
9
|
+
* sync registry share one source of truth without a runtime module cycle
|
|
10
|
+
* (registry → project-config → this module would cycle if this module
|
|
11
|
+
* imported the registry).
|
|
12
|
+
* @module core/deploy-status-sync
|
|
13
|
+
*/
|
|
14
|
+
import { type JsonValue } from "../sync/json-path.js";
|
|
15
|
+
/** Tracker vendors whose done vocabulary can bind a deploy ladder. */
|
|
16
|
+
export type Tracker = "jira" | "github" | "linear";
|
|
17
|
+
/** One environment step of the deploy ladder. */
|
|
18
|
+
export interface DeployLadderRung {
|
|
19
|
+
/** Environment name as configured (e.g. `dev`, `prod`) */
|
|
20
|
+
readonly env: string;
|
|
21
|
+
/** Git branch that deploys to this environment */
|
|
22
|
+
readonly branch: string;
|
|
23
|
+
/** Tracker status/label a work item earns when deployed here */
|
|
24
|
+
readonly doneStatus: string;
|
|
25
|
+
}
|
|
26
|
+
/** The resolved promotion ladder, ordered lowest environment first. */
|
|
27
|
+
export interface DeployLadder {
|
|
28
|
+
/** Ordered rungs (lowest environment first) */
|
|
29
|
+
readonly rungs: readonly DeployLadderRung[];
|
|
30
|
+
/** True when the ladder collapsed to a single terminal rung */
|
|
31
|
+
readonly terminalOnly: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Optional machine-written `deployStatusSync` section of the config. */
|
|
34
|
+
export interface DeployStatusSyncConfig {
|
|
35
|
+
/** Provisioning tier chosen at setup time */
|
|
36
|
+
readonly tier?: string;
|
|
37
|
+
/** Provisioned tracker artifact ids, keyed by artifact name */
|
|
38
|
+
readonly provisioned?: Readonly<Record<string, string>>;
|
|
39
|
+
/** How Linear deploy statuses are represented */
|
|
40
|
+
readonly linearBinding?: "labels" | "states";
|
|
41
|
+
/** ISO-8601 UTC timestamp of the last successful verification */
|
|
42
|
+
readonly verifiedAt?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Default env-keyed done labels for label-driven trackers (GitHub, Linear).
|
|
46
|
+
* Mirrored into the sync registry's `github.labels` / `linear.labels`
|
|
47
|
+
* defaults — keep value-identical with what `lisa sync` populates.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ENV_DONE_LABEL_DEFAULTS: {
|
|
50
|
+
readonly dev: "status:on-dev";
|
|
51
|
+
readonly staging: "status:on-stg";
|
|
52
|
+
readonly production: "status:done";
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Default env-keyed done statuses for JIRA workflows. Mirrored into the sync
|
|
56
|
+
* registry's `jira.workflow` default — keep value-identical with what
|
|
57
|
+
* `lisa sync` populates.
|
|
58
|
+
*/
|
|
59
|
+
export declare const JIRA_DONE_STATUS_DEFAULTS: {
|
|
60
|
+
readonly dev: "On Dev";
|
|
61
|
+
readonly staging: "On Stg";
|
|
62
|
+
readonly production: "Done";
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the deploy promotion ladder for a tracker from parsed config.
|
|
66
|
+
*
|
|
67
|
+
* The env universe is the keys of `deploy.branches`, joined with the
|
|
68
|
+
* tracker's done vocabulary (configured entries win over the default
|
|
69
|
+
* tables). `prod` ↔ `production` alias as one env iff exactly one spelling
|
|
70
|
+
* appears across the configured surfaces (branches, done map, order); the
|
|
71
|
+
* rung reports the configured spelling. Absent or empty `deploy` yields an
|
|
72
|
+
* empty ladder, never an error.
|
|
73
|
+
* @param config - Parsed `.lisa.config.json` value
|
|
74
|
+
* @param tracker - Tracker whose done vocabulary binds the ladder
|
|
75
|
+
* @param source - Config source shown in errors
|
|
76
|
+
* @returns The ordered ladder with its terminal-only collapse flag
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveDeployLadder(config: JsonValue, tracker: Tracker, source: string): DeployLadder;
|
|
79
|
+
/**
|
|
80
|
+
* Validate the optional `deployStatusSync` config section. Unknown fields
|
|
81
|
+
* inside the section are ignored in the typed view; the raw JSON round-trip
|
|
82
|
+
* preserves them.
|
|
83
|
+
* @param value - Untrusted section value
|
|
84
|
+
* @param source - Config source shown in errors
|
|
85
|
+
* @returns Typed section, or undefined when absent
|
|
86
|
+
*/
|
|
87
|
+
export declare function validateDeployStatusSyncConfig(value: unknown, source: string): DeployStatusSyncConfig | undefined;
|
|
88
|
+
//# sourceMappingURL=deploy-status-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-status-sync.d.ts","sourceRoot":"","sources":["../../src/core/deploy-status-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAG9B,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,iDAAiD;IACjD,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7C,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;CAI1B,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,yBAAyB;;;;CAI5B,CAAC;AA8MX;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GACb,YAAY,CAkCd;AA2ED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,GACb,sBAAsB,GAAG,SAAS,CAiBpC"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy-status-sync ladder resolution and config schema (DSS-1).
|
|
3
|
+
*
|
|
4
|
+
* Pure, fs-free helpers that turn a parsed `.lisa.config.json` value into the
|
|
5
|
+
* ordered environment ladder (env → deploy branch → tracker done status) the
|
|
6
|
+
* deploy-status-sync flows walk, plus the validator for the optional
|
|
7
|
+
* `deployStatusSync` section. The default done vocabularies live here — and
|
|
8
|
+
* are consumed by `src/sync/registry.ts` — so the ladder resolver and the
|
|
9
|
+
* sync registry share one source of truth without a runtime module cycle
|
|
10
|
+
* (registry → project-config → this module would cycle if this module
|
|
11
|
+
* imported the registry).
|
|
12
|
+
* @module core/deploy-status-sync
|
|
13
|
+
*/
|
|
14
|
+
import { getAtPath, isJsonObject, } from "../sync/json-path.js";
|
|
15
|
+
import { optionalString } from "./config-field-validation.js";
|
|
16
|
+
/**
|
|
17
|
+
* Default env-keyed done labels for label-driven trackers (GitHub, Linear).
|
|
18
|
+
* Mirrored into the sync registry's `github.labels` / `linear.labels`
|
|
19
|
+
* defaults — keep value-identical with what `lisa sync` populates.
|
|
20
|
+
*/
|
|
21
|
+
export const ENV_DONE_LABEL_DEFAULTS = {
|
|
22
|
+
dev: "status:on-dev",
|
|
23
|
+
staging: "status:on-stg",
|
|
24
|
+
production: "status:done",
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Default env-keyed done statuses for JIRA workflows. Mirrored into the sync
|
|
28
|
+
* registry's `jira.workflow` default — keep value-identical with what
|
|
29
|
+
* `lisa sync` populates.
|
|
30
|
+
*/
|
|
31
|
+
export const JIRA_DONE_STATUS_DEFAULTS = {
|
|
32
|
+
dev: "On Dev",
|
|
33
|
+
staging: "On Stg",
|
|
34
|
+
production: "Done",
|
|
35
|
+
};
|
|
36
|
+
/** Config dot-path of each tracker's env-keyed done vocabulary. */
|
|
37
|
+
const DONE_MAP_PATHS = {
|
|
38
|
+
github: "github.labels.build.done",
|
|
39
|
+
linear: "linear.labels.build.done",
|
|
40
|
+
jira: "jira.workflow.done",
|
|
41
|
+
};
|
|
42
|
+
/** Default done vocabulary per tracker, used when config has no entry. */
|
|
43
|
+
const DONE_DEFAULTS = {
|
|
44
|
+
github: ENV_DONE_LABEL_DEFAULTS,
|
|
45
|
+
linear: ENV_DONE_LABEL_DEFAULTS,
|
|
46
|
+
jira: JIRA_DONE_STATUS_DEFAULTS,
|
|
47
|
+
};
|
|
48
|
+
/** Canonical promotion rank when `deploy.order` is absent (lowest first). */
|
|
49
|
+
const CANONICAL_ENV_RANKS = {
|
|
50
|
+
dev: 0,
|
|
51
|
+
staging: 1,
|
|
52
|
+
production: 2,
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Keep only the string-valued entries of a JSON object record.
|
|
56
|
+
* @param value - JSON object record
|
|
57
|
+
* @returns Record of the string-valued entries
|
|
58
|
+
*/
|
|
59
|
+
function stringRecord(value) {
|
|
60
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read `deploy.branches` as an env → branch map, ignoring non-string values.
|
|
64
|
+
* @param config - Parsed config value
|
|
65
|
+
* @returns Env-to-branch record (empty when the section is absent)
|
|
66
|
+
*/
|
|
67
|
+
function readBranches(config) {
|
|
68
|
+
const branches = getAtPath(config, "deploy.branches");
|
|
69
|
+
return isJsonObject(branches) ? stringRecord(branches) : {};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read the tracker's configured done vocabulary from config, if any.
|
|
73
|
+
* @param config - Parsed config value
|
|
74
|
+
* @param tracker - Tracker whose vocabulary path applies
|
|
75
|
+
* @returns Configured env-keyed map, terminal string, or neither
|
|
76
|
+
*/
|
|
77
|
+
function readConfiguredDone(config, tracker) {
|
|
78
|
+
const done = getAtPath(config, DONE_MAP_PATHS[tracker]);
|
|
79
|
+
if (typeof done === "string")
|
|
80
|
+
return { terminal: done };
|
|
81
|
+
if (!isJsonObject(done))
|
|
82
|
+
return {};
|
|
83
|
+
return { map: stringRecord(done) };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Reject a configured done entry whose environment has no deploy branch.
|
|
87
|
+
* Only explicitly-configured entries can trigger this — default-table
|
|
88
|
+
* entries are fallbacks and never error.
|
|
89
|
+
* @param doneMap - Configured env-keyed done map
|
|
90
|
+
* @param branchEnvs - Environments present in `deploy.branches`
|
|
91
|
+
* @param source - Config source shown in errors
|
|
92
|
+
*/
|
|
93
|
+
function assertDoneEnvsHaveBranches(doneMap, branchEnvs, source) {
|
|
94
|
+
for (const [env, status] of Object.entries(doneMap)) {
|
|
95
|
+
if (!branchEnvs.includes(env)) {
|
|
96
|
+
throw new Error(`Invalid deploy configuration in ${source}: a done status ("${status}") is configured for environment "${env}", but deploy.branches has no "${env}" entry. Add deploy.branches.${env} (the git branch that deploys to ${env}) or remove "${env}" from the done map.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Read `deploy.order` as a string array, if present. A non-string entry is a
|
|
102
|
+
* decision-ready error, never silently dropped — dropping one would silently
|
|
103
|
+
* change the promotion order.
|
|
104
|
+
* @param config - Parsed config value
|
|
105
|
+
* @param source - Config source shown in errors
|
|
106
|
+
* @returns Ordered env names (lowest first), or undefined when absent
|
|
107
|
+
*/
|
|
108
|
+
function readOrder(config, source) {
|
|
109
|
+
const order = getAtPath(config, "deploy.order");
|
|
110
|
+
if (!Array.isArray(order))
|
|
111
|
+
return undefined;
|
|
112
|
+
return order.map(entry => {
|
|
113
|
+
if (typeof entry !== "string") {
|
|
114
|
+
throw new Error(`Invalid deploy.order in ${source}: every entry must be an environment name string; found ${JSON.stringify(entry)}.`);
|
|
115
|
+
}
|
|
116
|
+
return entry;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Require `deploy.order` to name exactly the `deploy.branches` env set, with
|
|
121
|
+
* no duplicate entries — a duplicate would otherwise survive set comparison
|
|
122
|
+
* and produce duplicate rungs.
|
|
123
|
+
* @param order - Configured order
|
|
124
|
+
* @param branchEnvs - Environments present in `deploy.branches` (dupe-free)
|
|
125
|
+
* @param source - Config source shown in errors
|
|
126
|
+
*/
|
|
127
|
+
function assertOrderMatchesBranches(order, branchEnvs, source) {
|
|
128
|
+
const orderSet = new Set(order);
|
|
129
|
+
const matches = orderSet.size === order.length &&
|
|
130
|
+
orderSet.size === branchEnvs.length &&
|
|
131
|
+
branchEnvs.every(env => orderSet.has(env));
|
|
132
|
+
if (!matches) {
|
|
133
|
+
throw new Error(`Invalid deploy.order in ${source}: its environment names must exactly match the keys of deploy.branches. deploy.order has [${order.join(", ")}]; deploy.branches has [${branchEnvs.join(", ")}].`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Order the configured environments lowest-first. `deploy.order` wins when
|
|
138
|
+
* present; otherwise the canonical `dev < staging < production` rank applies
|
|
139
|
+
* and any unrankable custom name (with more than one env) is a
|
|
140
|
+
* decision-ready error rather than a guess.
|
|
141
|
+
* @param branchEnvs - Environments present in `deploy.branches`
|
|
142
|
+
* @param order - Configured `deploy.order`, if any
|
|
143
|
+
* @param canonical - Alias-aware canonical name resolver
|
|
144
|
+
* @param source - Config source shown in errors
|
|
145
|
+
* @returns Environments ordered lowest first
|
|
146
|
+
*/
|
|
147
|
+
function orderEnvironments(branchEnvs, order, canonical, source) {
|
|
148
|
+
if (order !== undefined) {
|
|
149
|
+
assertOrderMatchesBranches(order, branchEnvs, source);
|
|
150
|
+
return order;
|
|
151
|
+
}
|
|
152
|
+
if (branchEnvs.length <= 1)
|
|
153
|
+
return branchEnvs;
|
|
154
|
+
const ranked = branchEnvs.map(env => {
|
|
155
|
+
const rank = CANONICAL_ENV_RANKS[canonical(env)];
|
|
156
|
+
if (rank === undefined) {
|
|
157
|
+
throw new Error(`Invalid deploy configuration in ${source}: cannot order environment "${env}". Set deploy.order (environments listed lowest first, e.g. ["dev","staging","production"]) so Lisa knows the promotion order.`);
|
|
158
|
+
}
|
|
159
|
+
return { env, rank };
|
|
160
|
+
});
|
|
161
|
+
return [...ranked].sort((a, b) => a.rank - b.rank).map(entry => entry.env);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Join ordered environments with their branch and done status. An env whose
|
|
165
|
+
* done status resolves nowhere (possible only for custom names outside the
|
|
166
|
+
* default tables) is skipped — a branch-only env is a deploy target without
|
|
167
|
+
* tracker vocabulary, not an error.
|
|
168
|
+
* @param orderedEnvs - Environments ordered lowest first
|
|
169
|
+
* @param branches - Env-to-branch record
|
|
170
|
+
* @param done - Configured done vocabulary
|
|
171
|
+
* @param defaults - Tracker default done vocabulary
|
|
172
|
+
* @param canonical - Alias-aware canonical name resolver
|
|
173
|
+
* @param terminalEnv - Highest-ranked env of the configured universe
|
|
174
|
+
* @returns The joined rungs, lowest environment first
|
|
175
|
+
*/
|
|
176
|
+
function buildRungs(orderedEnvs, branches, done, defaults, canonical, terminalEnv) {
|
|
177
|
+
return orderedEnvs.flatMap(env => {
|
|
178
|
+
const branch = branches[env];
|
|
179
|
+
const terminal = env === terminalEnv ? done.terminal : undefined;
|
|
180
|
+
const doneStatus = done.map?.[env] ?? terminal ?? defaults[canonical(env)];
|
|
181
|
+
return doneStatus === undefined || branch === undefined
|
|
182
|
+
? []
|
|
183
|
+
: [{ env, branch, doneStatus }];
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the deploy promotion ladder for a tracker from parsed config.
|
|
188
|
+
*
|
|
189
|
+
* The env universe is the keys of `deploy.branches`, joined with the
|
|
190
|
+
* tracker's done vocabulary (configured entries win over the default
|
|
191
|
+
* tables). `prod` ↔ `production` alias as one env iff exactly one spelling
|
|
192
|
+
* appears across the configured surfaces (branches, done map, order); the
|
|
193
|
+
* rung reports the configured spelling. Absent or empty `deploy` yields an
|
|
194
|
+
* empty ladder, never an error.
|
|
195
|
+
* @param config - Parsed `.lisa.config.json` value
|
|
196
|
+
* @param tracker - Tracker whose done vocabulary binds the ladder
|
|
197
|
+
* @param source - Config source shown in errors
|
|
198
|
+
* @returns The ordered ladder with its terminal-only collapse flag
|
|
199
|
+
*/
|
|
200
|
+
export function resolveDeployLadder(config, tracker, source) {
|
|
201
|
+
const branches = readBranches(config);
|
|
202
|
+
const branchEnvs = Object.keys(branches);
|
|
203
|
+
if (branchEnvs.length === 0)
|
|
204
|
+
return { rungs: [], terminalOnly: false };
|
|
205
|
+
const done = readConfiguredDone(config, tracker);
|
|
206
|
+
const order = readOrder(config, source);
|
|
207
|
+
const union = new Set([
|
|
208
|
+
...branchEnvs,
|
|
209
|
+
...Object.keys(done.map ?? {}),
|
|
210
|
+
...(order ?? []),
|
|
211
|
+
]);
|
|
212
|
+
const soleProdAlias = union.has("prod") && !union.has("production");
|
|
213
|
+
const canonical = (env) => soleProdAlias && env === "prod" ? "production" : env;
|
|
214
|
+
if (done.map !== undefined) {
|
|
215
|
+
assertDoneEnvsHaveBranches(done.map, branchEnvs, source);
|
|
216
|
+
}
|
|
217
|
+
const ordered = orderEnvironments(branchEnvs, order, canonical, source);
|
|
218
|
+
const terminalEnv = ordered.at(-1);
|
|
219
|
+
const rungs = buildRungs(ordered, branches, done, DONE_DEFAULTS[tracker], canonical, terminalEnv);
|
|
220
|
+
// terminalOnly guards DSS-2's native closure: a sole surviving LOWER rung
|
|
221
|
+
// (skip-collapsed ladder) must never be treated as the terminal env.
|
|
222
|
+
const terminalOnly = rungs.length === 1 &&
|
|
223
|
+
rungs[0] !== undefined &&
|
|
224
|
+
rungs[0].env === terminalEnv;
|
|
225
|
+
return { rungs, terminalOnly };
|
|
226
|
+
}
|
|
227
|
+
/** Strict ISO-8601 UTC timestamp (`Z` suffix, optional milliseconds). */
|
|
228
|
+
const ISO_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u;
|
|
229
|
+
/**
|
|
230
|
+
* Validate the optional provisioned-artifact id map.
|
|
231
|
+
* @param value - Untrusted field value
|
|
232
|
+
* @param source - Config source shown in errors
|
|
233
|
+
* @returns Valid id record or undefined
|
|
234
|
+
*/
|
|
235
|
+
function validateProvisioned(value, source) {
|
|
236
|
+
if (value === undefined)
|
|
237
|
+
return undefined;
|
|
238
|
+
if (!isJsonObject(value)) {
|
|
239
|
+
throw new Error(`Invalid deployStatusSync.provisioned in ${source}: expected an object`);
|
|
240
|
+
}
|
|
241
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, id]) => {
|
|
242
|
+
const valid = optionalString(id, source, `deployStatusSync.provisioned.${key}`);
|
|
243
|
+
return valid === undefined ? [] : [[key, valid]];
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Validate the optional Linear binding mode.
|
|
248
|
+
* @param value - Untrusted field value
|
|
249
|
+
* @param source - Config source shown in errors
|
|
250
|
+
* @returns Valid binding or undefined
|
|
251
|
+
*/
|
|
252
|
+
function validateLinearBinding(value, source) {
|
|
253
|
+
if (value === undefined)
|
|
254
|
+
return undefined;
|
|
255
|
+
if (value !== "labels" && value !== "states") {
|
|
256
|
+
throw new Error(`Invalid deployStatusSync.linearBinding in ${source}: expected "labels" or "states"`);
|
|
257
|
+
}
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Validate the optional verification timestamp as strict ISO-8601 UTC.
|
|
262
|
+
* @param value - Untrusted field value
|
|
263
|
+
* @param source - Config source shown in errors
|
|
264
|
+
* @returns Valid timestamp or undefined
|
|
265
|
+
*/
|
|
266
|
+
function validateVerifiedAt(value, source) {
|
|
267
|
+
if (value === undefined)
|
|
268
|
+
return undefined;
|
|
269
|
+
if (typeof value !== "string" ||
|
|
270
|
+
!ISO_UTC_PATTERN.test(value) ||
|
|
271
|
+
Number.isNaN(Date.parse(value))) {
|
|
272
|
+
throw new Error(`Invalid deployStatusSync.verifiedAt in ${source}: expected an ISO-8601 UTC timestamp (e.g. 2026-01-01T00:00:00Z)`);
|
|
273
|
+
}
|
|
274
|
+
return value;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Validate the optional `deployStatusSync` config section. Unknown fields
|
|
278
|
+
* inside the section are ignored in the typed view; the raw JSON round-trip
|
|
279
|
+
* preserves them.
|
|
280
|
+
* @param value - Untrusted section value
|
|
281
|
+
* @param source - Config source shown in errors
|
|
282
|
+
* @returns Typed section, or undefined when absent
|
|
283
|
+
*/
|
|
284
|
+
export function validateDeployStatusSyncConfig(value, source) {
|
|
285
|
+
if (value === undefined)
|
|
286
|
+
return undefined;
|
|
287
|
+
if (!isJsonObject(value)) {
|
|
288
|
+
throw new Error(`Invalid deployStatusSync in ${source}: expected an object`);
|
|
289
|
+
}
|
|
290
|
+
const tier = optionalString(value.tier, source, "deployStatusSync.tier");
|
|
291
|
+
const provisioned = validateProvisioned(value.provisioned, source);
|
|
292
|
+
const linearBinding = validateLinearBinding(value.linearBinding, source);
|
|
293
|
+
const verifiedAt = validateVerifiedAt(value.verifiedAt, source);
|
|
294
|
+
return {
|
|
295
|
+
...(tier === undefined ? {} : { tier }),
|
|
296
|
+
...(provisioned === undefined ? {} : { provisioned }),
|
|
297
|
+
...(linearBinding === undefined ? {} : { linearBinding }),
|
|
298
|
+
...(verifiedAt === undefined ? {} : { verifiedAt }),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=deploy-status-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-status-sync.js","sourceRoot":"","sources":["../../src/core/deploy-status-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Harness } from "./config.js";
|
|
2
|
+
import { type DeployStatusSyncConfig } from "./deploy-status-sync.js";
|
|
2
3
|
import { type VerificationConfig } from "./project-config-kane.js";
|
|
3
4
|
export type { BrowserVerificationConfig, KaneBrowserConfig, VerificationConfig, } from "./project-config-kane.js";
|
|
4
5
|
/** Filename of the per-project config, relative to the destination root */
|
|
@@ -40,6 +41,8 @@ export interface ProjectConfig {
|
|
|
40
41
|
readonly learnings?: LearningsConfig;
|
|
41
42
|
/** Optional empirical verification provider configuration. */
|
|
42
43
|
readonly verification?: VerificationConfig;
|
|
44
|
+
/** Optional machine-written deploy-status-sync provisioning record. */
|
|
45
|
+
readonly deployStatusSync?: DeployStatusSyncConfig;
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
45
48
|
* Resolve the validated project-rules path from config or its default.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-config.d.ts","sourceRoot":"","sources":["../../src/core/project-config.ts"],"names":[],"mappings":"AAYA,OAAO,EAML,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"project-config.d.ts","sourceRoot":"","sources":["../../src/core/project-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,8 @@ import * as fse from "fs-extra";
|
|
|
11
11
|
import { readFile, writeFile } from "node:fs/promises";
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { ACCEPTED_HARNESS_INPUTS, DEFAULT_HARNESS, HARNESS_ALIASES, HARNESS_VALUES, LEGACY_HARNESS_ALIASES, } from "./config.js";
|
|
14
|
+
import { validateSafeRelativeMarkdownPath } from "./config-field-validation.js";
|
|
15
|
+
import { validateDeployStatusSyncConfig, } from "./deploy-status-sync.js";
|
|
14
16
|
import { validateVerificationConfig, } from "./project-config-kane.js";
|
|
15
17
|
/** Filename of the per-project config, relative to the destination root */
|
|
16
18
|
export const PROJECT_CONFIG_FILENAME = ".lisa.config.json";
|
|
@@ -215,11 +217,13 @@ export function validateProjectConfig(parsed, configPath) {
|
|
|
215
217
|
: validateProjectRulesFile(obj.projectRulesFile, configPath);
|
|
216
218
|
const learnings = validateLearningsConfig(obj.learnings, configPath);
|
|
217
219
|
const verification = validateVerificationConfig(obj.verification, configPath);
|
|
220
|
+
const deployStatusSync = validateDeployStatusSyncConfig(obj.deployStatusSync, configPath);
|
|
218
221
|
return {
|
|
219
222
|
...(harness === undefined ? {} : { harness }),
|
|
220
223
|
...(projectRulesFile === undefined ? {} : { projectRulesFile }),
|
|
221
224
|
...(learnings === undefined ? {} : { learnings }),
|
|
222
225
|
...(verification === undefined ? {} : { verification }),
|
|
226
|
+
...(deployStatusSync === undefined ? {} : { deployStatusSync }),
|
|
223
227
|
};
|
|
224
228
|
}
|
|
225
229
|
/**
|
|
@@ -244,35 +248,6 @@ function validateOptionalHarness(value, configPath) {
|
|
|
244
248
|
const allowed = ACCEPTED_HARNESS_INPUTS.join(" | ");
|
|
245
249
|
throw new Error(`Invalid harness in ${configPath}: expected ${allowed}, got ${JSON.stringify(value)}`);
|
|
246
250
|
}
|
|
247
|
-
/**
|
|
248
|
-
* Validate a configurable destination as a safe, relative, non-traversing
|
|
249
|
-
* Markdown path. Shared by every path-typed config field; the `field` label is
|
|
250
|
-
* interpolated into each diagnostic so callers get a field-specific message.
|
|
251
|
-
* @param value - Raw configured path
|
|
252
|
-
* @param source - Config source for errors
|
|
253
|
-
* @param field - Config field name used in error messages
|
|
254
|
-
* @returns Validated project-relative path
|
|
255
|
-
*/
|
|
256
|
-
function validateSafeRelativeMarkdownPath(value, source, field) {
|
|
257
|
-
if (typeof value !== "string" ||
|
|
258
|
-
value.length === 0 ||
|
|
259
|
-
value.trim() !== value ||
|
|
260
|
-
value.includes("\\") ||
|
|
261
|
-
containsControlCharacter(value) ||
|
|
262
|
-
/^[a-z]:/iu.test(value) ||
|
|
263
|
-
path.posix.isAbsolute(value) ||
|
|
264
|
-
path.win32.isAbsolute(value)) {
|
|
265
|
-
throw new Error(`Invalid ${field} in ${source}: expected a safe relative POSIX path`);
|
|
266
|
-
}
|
|
267
|
-
const segments = value.split("/");
|
|
268
|
-
if (segments.some(segment => segment === "" || segment === "." || segment === "..")) {
|
|
269
|
-
throw new Error(`Invalid ${field} in ${source}: path traversal is not allowed`);
|
|
270
|
-
}
|
|
271
|
-
if (path.posix.extname(value).toLowerCase() !== ".md") {
|
|
272
|
-
throw new Error(`Invalid ${field} in ${source}: expected a Markdown file`);
|
|
273
|
-
}
|
|
274
|
-
return value;
|
|
275
|
-
}
|
|
276
251
|
/**
|
|
277
252
|
* Validate the configurable rules destination as a safe Markdown path, and
|
|
278
253
|
* reject the reserved learnings filename so a rules file can never collide with
|
|
@@ -335,17 +310,6 @@ function validateLearningsConfig(value, source) {
|
|
|
335
310
|
}
|
|
336
311
|
return { file: validateLearningsFile(file, source) };
|
|
337
312
|
}
|
|
338
|
-
/**
|
|
339
|
-
* Whether a path contains an ASCII control character.
|
|
340
|
-
* @param value - Configured path
|
|
341
|
-
* @returns True when any control character is present
|
|
342
|
-
*/
|
|
343
|
-
function containsControlCharacter(value) {
|
|
344
|
-
return Array.from(value).some(character => {
|
|
345
|
-
const code = character.charCodeAt(0);
|
|
346
|
-
return code <= 31 || code === 127;
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
313
|
/**
|
|
350
314
|
* Narrow an unknown value to the Harness type
|
|
351
315
|
* @param value - Untrusted value to test
|