@indigoai-us/hq-cli 5.282.0 → 5.284.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/CHANGELOG.md +32 -0
- package/assets/scaffold/.claude/hooks/validate-policy-frontmatter.sh +485 -0
- package/assets/scaffold/core/scripts/eval-trigger.sh +193 -0
- package/assets/scaffold/core/scripts/policy-age-report.sh +156 -0
- package/assets/scaffold/core/scripts/policy-retire.sh +199 -0
- package/assets/scaffold/core/scripts/read-policy-frontmatter.sh +32 -0
- package/dist/command-catalog.generated.d.ts +67 -0
- package/dist/command-catalog.generated.js +83 -0
- package/dist/commands/core-knowledge-prefs.d.ts +10 -0
- package/dist/commands/core-knowledge-prefs.js +125 -0
- package/dist/commands/core-native-policy-flag.d.ts +9 -0
- package/dist/commands/core-native-policy-flag.js +12 -0
- package/dist/commands/core-ontology-native.d.ts +0 -1
- package/dist/commands/core-ontology-native.js +2 -30
- package/dist/commands/core-ontology.d.ts +4 -0
- package/dist/commands/core-ontology.js +8 -3
- package/dist/commands/core-policy-frontmatter.d.ts +8 -0
- package/dist/commands/core-policy-frontmatter.js +52 -0
- package/dist/commands/core-policy.d.ts +15 -0
- package/dist/commands/core-policy.js +982 -0
- package/dist/commands/core.js +14 -1
- package/dist/commands/lanes-shared.d.ts +4 -1
- package/dist/commands/lanes-shared.js +2 -1
- package/dist/commands/lanes.d.ts +8 -0
- package/dist/commands/lanes.js +303 -91
- package/dist/fast-lanes-list-route.d.ts +8 -0
- package/dist/fast-lanes-list-route.js +30 -0
- package/dist/fast-lanes-list.d.ts +3 -0
- package/dist/fast-lanes-list.js +252 -0
- package/dist/fast-scaffold.d.ts +5 -0
- package/dist/fast-scaffold.js +10 -0
- package/dist/index.js +10 -1
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +24 -1
- package/dist/lib/checkpoint-ontology/merge-copies.js +70 -1
- package/dist/lib/lanes/flag-keys.d.ts +3 -0
- package/dist/lib/lanes/flag-keys.js +3 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/outpost-dispatch.d.ts +9 -1
- package/dist/lib/lanes/outpost-dispatch.js +14 -2
- package/dist/lib/lanes/outpost-hire-cache.d.ts +3 -0
- package/dist/lib/lanes/outpost-hire-cache.js +11 -3
- package/dist/lib/lanes/outpost-snapshot-cache.d.ts +26 -0
- package/dist/lib/lanes/outpost-snapshot-cache.js +215 -0
- package/dist/lib/lanes/store.d.ts +4 -0
- package/dist/lib/lanes/store.js +39 -0
- package/dist/lib/mesh/live/daemon/credentials.js +8 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +143 -5
- package/dist/lib/policy-validator.d.ts +2 -0
- package/dist/lib/policy-validator.js +136 -0
- package/dist/native-fast-route.d.ts +2 -0
- package/dist/native-fast-route.js +6 -2
- package/dist/utils/contribution-table.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const VALIDATION_NOTE = '\n(The HQ_ALLOW_POLICY_NO_TRIGGER override requires explicit human permission.\nAn agent must never set, export, or write it on its own initiative.)\n';
|
|
2
|
+
function isPolicyPath(file) {
|
|
3
|
+
const low = file.toLowerCase().replaceAll('\\', '/');
|
|
4
|
+
const basename = low.slice(low.lastIndexOf('/') + 1);
|
|
5
|
+
return low.endsWith('.md')
|
|
6
|
+
&& (low.includes('/policies/') || low.startsWith('policies/'))
|
|
7
|
+
&& basename !== 'readme.md'
|
|
8
|
+
&& !low.includes('/audit/');
|
|
9
|
+
}
|
|
10
|
+
function normalizeValidatorText(content) {
|
|
11
|
+
return content.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
|
12
|
+
}
|
|
13
|
+
function hasValidTriggerGrammar(expression) {
|
|
14
|
+
let input = expression.replace(/[A-Za-z0-9_./][A-Za-z0-9_./-]*/g, '0');
|
|
15
|
+
if (/[^01&|!() \t]/.test(input))
|
|
16
|
+
return false;
|
|
17
|
+
let position = 0;
|
|
18
|
+
let valid = true;
|
|
19
|
+
const skip = () => { while (input[position] === ' ' || input[position] === '\t')
|
|
20
|
+
position += 1; };
|
|
21
|
+
let parseOr;
|
|
22
|
+
let parseAnd;
|
|
23
|
+
let parseNot;
|
|
24
|
+
let parseAtom;
|
|
25
|
+
parseAtom = () => {
|
|
26
|
+
skip();
|
|
27
|
+
const current = input[position];
|
|
28
|
+
if (current === '(') {
|
|
29
|
+
position += 1;
|
|
30
|
+
const result = parseOr();
|
|
31
|
+
skip();
|
|
32
|
+
if (input[position] !== ')') {
|
|
33
|
+
valid = false;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
position += 1;
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
if (current === '0' || current === '1') {
|
|
40
|
+
position += 1;
|
|
41
|
+
return current === '1';
|
|
42
|
+
}
|
|
43
|
+
valid = false;
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
parseNot = () => {
|
|
47
|
+
skip();
|
|
48
|
+
if (input[position] === '!') {
|
|
49
|
+
position += 1;
|
|
50
|
+
return !parseNot();
|
|
51
|
+
}
|
|
52
|
+
return parseAtom();
|
|
53
|
+
};
|
|
54
|
+
parseAnd = () => {
|
|
55
|
+
let result = parseNot();
|
|
56
|
+
skip();
|
|
57
|
+
while (valid && input.slice(position, position + 2) === '&&') {
|
|
58
|
+
position += 2;
|
|
59
|
+
const rhs = parseNot();
|
|
60
|
+
result = result && rhs;
|
|
61
|
+
skip();
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
parseOr = () => {
|
|
66
|
+
let result = parseAnd();
|
|
67
|
+
skip();
|
|
68
|
+
while (valid && input.slice(position, position + 2) === '||') {
|
|
69
|
+
position += 2;
|
|
70
|
+
const rhs = parseAnd();
|
|
71
|
+
result = result || rhs;
|
|
72
|
+
skip();
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
input = input.replace(/\r/g, '');
|
|
77
|
+
if (input.replace(/[ \t]/g, '') === '')
|
|
78
|
+
return false;
|
|
79
|
+
parseOr();
|
|
80
|
+
skip();
|
|
81
|
+
return valid && position === input.length;
|
|
82
|
+
}
|
|
83
|
+
export function policyValidatorDiagnostic(content, filePath, env) {
|
|
84
|
+
if (!isPolicyPath(filePath))
|
|
85
|
+
return undefined;
|
|
86
|
+
const normalized = normalizeValidatorText(content);
|
|
87
|
+
const match = /^\s*---[ \t]*\n([\s\S]*?)\n---[ \t]*(\n|$)/.exec(normalized);
|
|
88
|
+
if (!match)
|
|
89
|
+
return `BLOCKED: policy file is missing required trigger frontmatter (missing: no-frontmatter).\n\nEvery policy under */policies/ must declare BOTH:\n when: <expression> # e.g. always | git && push | deploy || share\n on: [<events>] # any of PreToolUse, PostToolUse, UserPromptSubmit, AssistantIntent, SessionStart\nThese drive just-in-time policy injection. See\ncore/knowledge/public/hq-core/policies-spec.md ("Trigger Expressions").\nAdd both fields to the frontmatter, then retry.\n${VALIDATION_NOTE}`;
|
|
90
|
+
const frontmatter = match[1];
|
|
91
|
+
const whenLines = [...frontmatter.matchAll(/^[ \t]*when:[ \t]*(.*)$/gm)].map((entry) => entry[1] ?? '');
|
|
92
|
+
const hasWhen = whenLines.some((line) => /\S/.test(line));
|
|
93
|
+
const onMatch = /^[ \t]*on:[ \t]*\S/m.test(frontmatter);
|
|
94
|
+
const missing = [];
|
|
95
|
+
if (!hasWhen)
|
|
96
|
+
missing.push('when');
|
|
97
|
+
if (!onMatch)
|
|
98
|
+
missing.push('on');
|
|
99
|
+
if (missing.length > 0)
|
|
100
|
+
return `BLOCKED: policy file is missing required trigger frontmatter (missing: ${missing.join(',')}).\n\nEvery policy under */policies/ must declare BOTH:\n when: <expression> # e.g. always | git && push | deploy || share\n on: [<events>] # any of PreToolUse, PostToolUse, UserPromptSubmit, AssistantIntent, SessionStart\nThese drive just-in-time policy injection. See\ncore/knowledge/public/hq-core/policies-spec.md ("Trigger Expressions").\nAdd both fields to the frontmatter, then retry.\n${VALIDATION_NOTE}`;
|
|
101
|
+
const enforcement = /^[ \t]*enforcement:[ \t]*["']?([A-Za-z]+)["']?/m.exec(frontmatter)?.[1]?.toLowerCase();
|
|
102
|
+
if (enforcement === 'hard') {
|
|
103
|
+
const tautological = whenLines.some((line) => !/[&!]/.test(line.toLowerCase()) && /(^|[^A-Za-z0-9_./-])always([^A-Za-z0-9_./-]|$)/i.test(line));
|
|
104
|
+
const onLine = /^[ \t]*on:[ \t]*(.*)$/m.exec(frontmatter)?.[1] ?? '';
|
|
105
|
+
const reactive = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'AssistantIntent'].filter((event) => onLine.includes(event));
|
|
106
|
+
if (tautological && reactive.length > 0) {
|
|
107
|
+
const events = reactive.join(', ');
|
|
108
|
+
return `BLOCKED: an enforcement: hard policy pairs an unconditional \`when:\` with a\nreactive event (${events}).\n\n\`when: always\` is TRUE for every command, prompt, and message. On a reactive\nevent the injector ranks that as an event-specific match, so it takes the\nsession cap slot of a policy that genuinely matched the thing you are doing —\nthe loose trigger crowds out the precise one.\n\nPick one:\n when: always + on: [SessionStart] # ambient governance rule\n when: <real signal> + on: [PreToolUse, ...] # e.g. deploy || publish\n\nName the word(s) that actually appear when the rule is relevant. See\ncore/knowledge/public/hq-core/policies-spec.md ("Trigger Expressions").\n${VALIDATION_NOTE}`;
|
|
109
|
+
}
|
|
110
|
+
const after = normalized.slice(match.index + match[0].length);
|
|
111
|
+
const binding = [];
|
|
112
|
+
const stop = /^#+[ \t]*(rationale|rationale and context|background|change history|changelog|history|examples?|references?|related|see also|sources?|provenance|evidence)[ \t]*$/i;
|
|
113
|
+
for (const line of after.split('\n')) {
|
|
114
|
+
if (stop.test(line.trim().toLowerCase()))
|
|
115
|
+
break;
|
|
116
|
+
binding.push(line);
|
|
117
|
+
}
|
|
118
|
+
const byteCount = Buffer.byteLength(binding.join('\n').trim(), 'utf8');
|
|
119
|
+
const rawMax = env.HQ_POLICY_HARD_RULE_MAX_BYTES || '6144';
|
|
120
|
+
const max = Number.parseInt(rawMax, 10);
|
|
121
|
+
if (Number.isFinite(max) && max > 0 && byteCount > max) {
|
|
122
|
+
return `BLOCKED: enforcement: hard policy body is ${byteCount} bytes; the limit is ${rawMax}.\n\nA hard policy is injected VERBATIM into the session, so its length is a\nrecurring context cost for every session it fires in. Keep the binding rule\ntight and move the reasoning out of the injected span: everything from the\nfirst \`## Rationale\` / \`## Background\` / \`## Change history\` /\n\`## Examples\` / \`## References\` heading onward is NOT counted and NOT\ninjected, so long-form context belongs there.\n\nIf the rule genuinely needs more than that, either split it into separate\npolicies with distinct triggers, or raise HQ_POLICY_HARD_RULE_MAX_BYTES in\n.claude/settings.local.json "env".\n${VALIDATION_NOTE}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let malformedWhen;
|
|
126
|
+
for (const when of whenLines) {
|
|
127
|
+
if (!hasValidTriggerGrammar(when))
|
|
128
|
+
malformedWhen = when;
|
|
129
|
+
}
|
|
130
|
+
if (malformedWhen !== undefined) {
|
|
131
|
+
const when = malformedWhen;
|
|
132
|
+
return `BLOCKED: policy file has a malformed when: trigger expression:\n\n when: ${when}\n\nThe canonical trigger evaluator rejected it. A space is NOT an AND, and quoted\nphrases are NOT tokens. Use explicit boolean operators instead:\n "pull request" -> (pull && request)\n "force-push" -> force-push\n\nUse only identifiers joined by the documented boolean grammar:\n when: <identifier> # e.g. always | git | /deep-plan\n when: <expr> && <expr> # AND\n when: <expr> || <expr> # OR\n when: ! <expr> # NOT\n when: ( <expr> ) # grouping\nIdentifiers may contain letters, digits, _, ., /, and internal -. Quotes,\nYAML block scalars, adjacent identifiers without an operator, and other\npunctuation are not valid. See core/knowledge/public/hq-core/policies-spec.md\n("Trigger Expressions"). Fix the expression, then retry.\n${VALIDATION_NOTE}`;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=policy-validator.js.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
/** Default-off flag check for native entrypoint routes. */
|
|
2
2
|
export declare function nativeFastRouteEnabled(argv: readonly string[], env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
3
|
+
/** Reads a default-off fast-route gate from its local override or fresh snapshot. */
|
|
4
|
+
export declare function fastFlagEnabled(flagKey: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
3
5
|
//# sourceMappingURL=native-fast-route.d.ts.map
|
|
@@ -5,7 +5,11 @@ export async function nativeFastRouteEnabled(argv, env = process.env) {
|
|
|
5
5
|
const parsed = parseFastCore(argv);
|
|
6
6
|
if (!parsed || parsed.entry.kind !== "native")
|
|
7
7
|
return false;
|
|
8
|
-
|
|
8
|
+
return fastFlagEnabled(parsed.entry.flagKey, env);
|
|
9
|
+
}
|
|
10
|
+
/** Reads a default-off fast-route gate from its local override or fresh snapshot. */
|
|
11
|
+
export async function fastFlagEnabled(flagKey, env = process.env) {
|
|
12
|
+
const override = nativeRouteOverride(flagKey, env);
|
|
9
13
|
if (override !== null)
|
|
10
14
|
return override;
|
|
11
15
|
try {
|
|
@@ -16,7 +20,7 @@ export async function nativeFastRouteEnabled(argv, env = process.env) {
|
|
|
16
20
|
const { companyUid } = resolveProcessFlagCompanyContext(env);
|
|
17
21
|
if (!companyUid)
|
|
18
22
|
return false;
|
|
19
|
-
return readFlagSnapshotFile(companyUid, { env })?.flags[
|
|
23
|
+
return readFlagSnapshotFile(companyUid, { env })?.flags[flagKey] === true;
|
|
20
24
|
}
|
|
21
25
|
catch {
|
|
22
26
|
// The new path is a rollout optimization. Any unavailable local scope or
|
|
@@ -99,5 +99,5 @@ export declare const CONTRIBUTION_KEYS: ContributionKey[];
|
|
|
99
99
|
*/
|
|
100
100
|
export declare function payloadFor(key: ContributionKey, item: string): string;
|
|
101
101
|
/** Keys whose contributions are wired by a host symlink (skip `merge` rows). */
|
|
102
|
-
export declare const SYMLINK_KEYS: ("workers" | "mcp" | "commands" | "
|
|
102
|
+
export declare const SYMLINK_KEYS: ("workers" | "mcp" | "commands" | "hooks" | "skills" | "policies" | "knowledge" | "scripts")[];
|
|
103
103
|
//# sourceMappingURL=contribution-table.d.ts.map
|