@entrorove/skill 0.1.38 → 0.1.40
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/CAPABILITY-MAP.md +5 -2
- package/CHANGELOG.md +10 -0
- package/IDE-EXECUTION.md +6 -0
- package/SKILL.md +7 -3
- package/break-glass.mjs +56 -0
- package/cli.mjs +11 -13
- package/ide-command.mjs +4 -1
- package/ide-hook.mjs +87 -26
- package/ide-install.mjs +3 -2
- package/ide-queue.mjs +108 -0
- package/ide-results.mjs +2 -1
- package/ide-session.mjs +14 -2
- package/local-cli.mjs +2 -0
- package/local-contracts.mjs +2 -0
- package/local-execution.mjs +2 -0
- package/local-flow-lifecycle.mjs +95 -0
- package/local-scope-config-imports.mjs +14 -9
- package/local-scope-files.mjs +32 -11
- package/local-scope-repository.mjs +2 -1
- package/local-task-routing.mjs +27 -0
- package/manifest.json +1 -1
- package/package.json +4 -1
- package/release-state.json +25 -22
- package/release.mjs +3 -1
- package/releases.json +12 -0
- package/remote-client.mjs +1 -1
- package/reuse-discipline-release.mjs +1 -1
package/ide-results.mjs
CHANGED
|
@@ -14,9 +14,10 @@ export function parseIdeResults(response) {
|
|
|
14
14
|
function resultMetadata(value, depth = 0) {
|
|
15
15
|
if (!value || typeof value !== 'object' || depth > 3) return undefined;
|
|
16
16
|
const metadata = {};
|
|
17
|
-
for (const key of ['schemaVersion', 'requestId', 'flowId', 'status', 'taskId', 'taskNumber', 'taskStatus']) {
|
|
17
|
+
for (const key of ['schemaVersion', 'requestId', 'flowId', 'status', 'operation', 'termination', 'flowStatus', 'taskId', 'taskNumber', 'taskStatus']) {
|
|
18
18
|
if (typeof value[key] === 'string' && value[key].length <= 200) metadata[key] = value[key];
|
|
19
19
|
}
|
|
20
|
+
if (typeof value.unbound === 'boolean') metadata.unbound = value.unbound;
|
|
20
21
|
for (const key of ['output', 'result']) if (value[key]) metadata[key] = resultMetadata(value[key], depth + 1);
|
|
21
22
|
for (const key of ['nextStep', 'nextAction']) if (typeof value[key]?.operation === 'string') metadata[key] = { operation: value[key].operation };
|
|
22
23
|
if (value.interaction) metadata.interaction = true;
|
package/ide-session.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { credentialDirectory, readCredential } from './local-credential.mjs';
|
|
6
6
|
import { withStateLock } from './local-state-lock.mjs';
|
|
7
7
|
import { requestTransport, parseTransportResponse } from './remote-transport.mjs';
|
|
8
|
+
import { breakGlassPath, breakGlassSchema, writeBreakGlass as writeOfflineBreakGlass } from './break-glass.mjs';
|
|
8
9
|
|
|
9
10
|
export const ideDigest = value => createHash('sha256').update(typeof value === 'string' ? value : JSON.stringify(value)).digest('hex');
|
|
10
11
|
export const ideError = (code, message) => Object.assign(new Error(message), { code });
|
|
@@ -32,15 +33,26 @@ export async function writeIdeJson(file, value) {
|
|
|
32
33
|
await fs.writeFile(stage, `${JSON.stringify(value)}\n`, { flag: 'wx', mode: 0o600 });
|
|
33
34
|
try { await fs.rename(stage, file); } finally { await fs.rm(stage, { force: true }); }
|
|
34
35
|
}
|
|
36
|
+
export const writeBreakGlass = writeOfflineBreakGlass;
|
|
37
|
+
export async function readBreakGlass(home = os.homedir()) {
|
|
38
|
+
await ideDirectory(home, true);
|
|
39
|
+
const value = await readIdeJson(breakGlassPath(home));
|
|
40
|
+
if (!value || value.schemaVersion !== breakGlassSchema || value.mode !== 'management-only'
|
|
41
|
+
|| typeof value.expiresAt !== 'string' || !Number.isFinite(Date.parse(value.expiresAt))
|
|
42
|
+
|| Date.parse(value.expiresAt) <= Date.now()) return null;
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
35
45
|
export async function ideSession(event, action, home = os.homedir()) {
|
|
36
46
|
if (typeof event.session_id !== 'string' || !/^[A-Za-z0-9_-]{1,200}$/u.test(event.session_id) || !path.isAbsolute(event.cwd ?? '')) throw ideError('ENTROROVE_IDE_IDENTITY', 'Host hook requires its actual session and repository');
|
|
37
47
|
const directory = await ideDirectory(home, true), file = path.join(directory, `session-${ideDigest(event.session_id)}.json`);
|
|
38
48
|
return withStateLock(file, async () => {
|
|
39
49
|
const root = await fs.realpath(event.cwd), prior = await readIdeJson(file);
|
|
40
50
|
if (prior && (prior.schemaVersion !== 'entrorove.ide-session/1.0' || prior.sessionId !== event.session_id || prior.repositoryRoot !== root)) throw ideError('ENTROROVE_IDE_IDENTITY', 'A session cannot replace its original physical repository');
|
|
41
|
-
const state = prior ?? { schemaVersion: 'entrorove.ide-session/1.0', sessionId: event.session_id, repositoryRoot: root, active: false, phase: 'entry', seen: [] };
|
|
51
|
+
const state = prior ?? { schemaVersion: 'entrorove.ide-session/1.0', sessionId: event.session_id, repositoryRoot: root, active: false, phase: 'entry', seen: [], blockStreak: 0 };
|
|
52
|
+
state.stateFile = file;
|
|
42
53
|
const result = await action(state);
|
|
43
|
-
|
|
54
|
+
const { stateFile, ...persisted } = state;
|
|
55
|
+
await writeIdeJson(file, { ...persisted, observedAt: new Date().toISOString() });
|
|
44
56
|
return result;
|
|
45
57
|
});
|
|
46
58
|
}
|
package/local-cli.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
LOCAL_LEDGER_OPERATIONS,
|
|
13
13
|
LOCAL_RENEWAL_OPERATIONS, LOCAL_PROBE_OPERATIONS,
|
|
14
14
|
LOCAL_TASK_ROUTING_OPERATIONS,
|
|
15
|
+
LOCAL_FLOW_LIFECYCLE_OPERATIONS,
|
|
15
16
|
} from './local-execution.mjs';
|
|
16
17
|
import { validateLocalInput } from './local-contracts.mjs';
|
|
17
18
|
import { prepareMergeArtifacts } from './local-merge-artifacts.mjs';
|
|
@@ -58,6 +59,7 @@ const operations = {
|
|
|
58
59
|
...LOCAL_LEDGER_OPERATIONS, ...LOCAL_CHAIN_OPERATIONS, ...LOCAL_BUDGET_TRANSITION_OPERATIONS,
|
|
59
60
|
...LOCAL_RENEWAL_OPERATIONS, ...LOCAL_PROBE_OPERATIONS,
|
|
60
61
|
...LOCAL_TASK_ROUTING_OPERATIONS,
|
|
62
|
+
...LOCAL_FLOW_LIFECYCLE_OPERATIONS,
|
|
61
63
|
...ARCHGUARD_LOCAL_OPERATIONS,
|
|
62
64
|
...LOCAL_BUDGET_HANDOFF_OPERATIONS,
|
|
63
65
|
...CALCTOOL_LOCAL_OPERATIONS,
|
package/local-contracts.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { array, binding, change, confirmation, enumeration, evidence, evidenceAr
|
|
|
15
15
|
const schemas = {};
|
|
16
16
|
const base = { repositoryRoot: text, home: text };
|
|
17
17
|
const flow = { ...base, flowId: identifier };
|
|
18
|
+
const flowLifecycle = { ...base, flowId: identifier, reason: { ...text, minLength: 1, maxLength: 500, pattern: '\\S' } };
|
|
18
19
|
const fixedRoundingPolicy = object({ decimalPlaces: { const: 2 }, mode: { const: 'half-away-from-zero' } }, ['decimalPlaces', 'mode']);
|
|
19
20
|
const step = { ...flow, stepId: identifier };
|
|
20
21
|
function add(names, properties, required = [], alternatives) {
|
|
@@ -24,6 +25,7 @@ function add(names, properties, required = [], alternatives) {
|
|
|
24
25
|
for (const name of names.split('|')) schemas[name] = schema;
|
|
25
26
|
}
|
|
26
27
|
add('capabilities|help', {});
|
|
28
|
+
add('flow-cancel|flow-abandon', flowLifecycle, ['repositoryRoot', 'flowId']);
|
|
27
29
|
add('snapshot-plan', { ...step, paths, snapshotId: identifier, contractDigest: nullable(sha256), handoffCandidate: HANDOFF_SNAPSHOT_CANDIDATE_SCHEMA }, ['repositoryRoot', 'flowId', 'stepId', 'paths']);
|
|
28
30
|
add('snapshot-verify', { ...step, snapshotId: identifier }, ['repositoryRoot', 'flowId', 'stepId', 'snapshotId']);
|
|
29
31
|
const gate = { ...step, taskId: identifier, mutationPass: record };
|
package/local-execution.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { receiverClaim } from './local-task-handoff-overlay-wire.mjs';
|
|
|
25
25
|
import { assertTaskHandoffActive, assertTaskHandoffSnapshotBinding, readTaskHandoffSnapshotBinding,
|
|
26
26
|
withTaskHandoffIssueFence, withTaskHandoffWriteFence } from './local-task-handoff-fence.mjs';
|
|
27
27
|
import { LOCAL_OPERATION_SCHEMAS } from './local-contracts.mjs';
|
|
28
|
+
import { LOCAL_FLOW_LIFECYCLE_OPERATIONS, flowCancel, flowAbandon } from './local-flow-lifecycle.mjs';
|
|
28
29
|
export { LOCAL_OPERATION_SCHEMAS };
|
|
29
30
|
export const LOCAL_SCHEMA = 'entrorove.local-execution/1.0';
|
|
30
31
|
export const SNAPSHOT_SCHEMA = 'entrorove.snapshot/1.0';
|
|
@@ -314,3 +315,4 @@ export { LOCAL_RENEWAL_OPERATIONS, LOCAL_PROBE_OPERATIONS };
|
|
|
314
315
|
export { branchCreate, branchList, branchSwitch, diffReport, preflight, ledgerQuery, appendLedgerRecord, chainInit, chainResume, chainStatus, chainAnswer };
|
|
315
316
|
export { budgetAutoRenewRequest, budgetAutoRenew, budgetAutoRenewStop, probe, reassess };
|
|
316
317
|
export { LOCAL_TASK_ROUTING_OPERATIONS, taskDescribe, messageRoute, messageAccept, taskResume };
|
|
318
|
+
export { LOCAL_FLOW_LIFECYCLE_OPERATIONS, flowCancel, flowAbandon };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { withStateLock } from './local-state-lock.mjs';
|
|
6
|
+
import { settleTaskFlow } from './local-task-routing.mjs';
|
|
7
|
+
|
|
8
|
+
export const FLOW_LIFECYCLE_SCHEMA = 'entrorove.flow-lifecycle/1.0';
|
|
9
|
+
function fail(code, message) { throw Object.assign(new Error(message), { code }); }
|
|
10
|
+
function digest(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
11
|
+
function canonical(value) {
|
|
12
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
|
|
13
|
+
if (value && typeof value === 'object') return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
|
|
14
|
+
return JSON.stringify(value);
|
|
15
|
+
}
|
|
16
|
+
function rootOf(value) {
|
|
17
|
+
if (typeof value !== 'string' || !path.isAbsolute(value)) fail('ENTROROVE_ROOT_INVALID', 'repositoryRoot must be absolute');
|
|
18
|
+
return fs.realpath(value);
|
|
19
|
+
}
|
|
20
|
+
function homeOf(value) {
|
|
21
|
+
const home = value ?? os.homedir();
|
|
22
|
+
if (typeof home !== 'string' || !path.isAbsolute(home)) fail('ENTROROVE_HOME_INVALID', 'home must be absolute');
|
|
23
|
+
return home;
|
|
24
|
+
}
|
|
25
|
+
async function privateDir(directory) {
|
|
26
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
27
|
+
const stat = await fs.lstat(directory);
|
|
28
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) fail('ENTROROVE_STATE_INVALID', 'flow lifecycle state directory must be a real directory');
|
|
29
|
+
if (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o700) await fs.chmod(directory, 0o700);
|
|
30
|
+
}
|
|
31
|
+
async function location(input) {
|
|
32
|
+
const root = await rootOf(input.repositoryRoot); const home = homeOf(input.home);
|
|
33
|
+
const repositoryDigest = digest(root).slice(0, 32);
|
|
34
|
+
const directory = path.join(home, '.entrorove', 'flow-lifecycle', repositoryDigest);
|
|
35
|
+
return { root, home, file: path.join(directory, 'state.json'), directory, repositoryDigest };
|
|
36
|
+
}
|
|
37
|
+
async function save(file, state) {
|
|
38
|
+
await privateDir(path.dirname(file));
|
|
39
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
40
|
+
await fs.writeFile(temporary, `${canonical(state)}\n`, { flag: 'wx', mode: 0o600 });
|
|
41
|
+
await fs.rename(temporary, file);
|
|
42
|
+
if (process.platform !== 'win32') await fs.chmod(file, 0o600);
|
|
43
|
+
}
|
|
44
|
+
async function load(input) {
|
|
45
|
+
const place = await location(input); await privateDir(place.directory);
|
|
46
|
+
try {
|
|
47
|
+
const stat = await fs.lstat(place.file);
|
|
48
|
+
if (!stat.isFile() || stat.isSymbolicLink()) fail('ENTROROVE_STATE_INVALID', 'flow lifecycle state must be a regular file');
|
|
49
|
+
const state = JSON.parse(await fs.readFile(place.file, 'utf8'));
|
|
50
|
+
if (state.schemaVersion !== FLOW_LIFECYCLE_SCHEMA || state.repositoryDigest !== place.repositoryDigest
|
|
51
|
+
|| !Array.isArray(state.flows) || !Array.isArray(state.records)) fail('ENTROROVE_STATE_INVALID', 'flow lifecycle state identity is invalid');
|
|
52
|
+
return { ...place, state };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.code !== 'ENOENT') throw error;
|
|
55
|
+
return { ...place, state: { schemaVersion: FLOW_LIFECYCLE_SCHEMA, repositoryDigest: place.repositoryDigest, sequence: 0, flows: [], records: [] } };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function identifier(value, label) {
|
|
59
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(value)) fail('ENTROROVE_ID_INVALID', `${label} must be a safe identifier`);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function reason(value, operation) {
|
|
63
|
+
if (value === undefined) return operation === 'flow-abandon' ? 'flow-abandoned-by-owner' : 'flow-cancelled-by-owner';
|
|
64
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 500 || /[\u0000-\u001f\u007f]/.test(value)) fail('ENTROROVE_INPUT_INVALID', 'reason must be 1 to 500 printable characters');
|
|
65
|
+
return value.trim();
|
|
66
|
+
}
|
|
67
|
+
async function runUnlocked(operation, input) {
|
|
68
|
+
const flowId = identifier(input.flowId, 'flowId'); const authority = await load(input);
|
|
69
|
+
const prior = authority.state.flows.find(flow => flow.flowId === flowId);
|
|
70
|
+
if (prior?.status === 'abandoned' || (prior?.status === 'cancelled' && operation === 'flow-cancel')) {
|
|
71
|
+
return { schemaVersion: 'entrorove.local-execution/1.0', status: 'reported', operation, flowId,
|
|
72
|
+
termination: prior.status === 'abandoned' ? 'abandoned' : 'cancelled', reason: prior.reason, unbound: prior.unbound === true, duplicate: true };
|
|
73
|
+
}
|
|
74
|
+
const now = new Date().toISOString(); const termination = operation === 'flow-abandon' ? 'abandoned' : 'cancelled';
|
|
75
|
+
const settledTask = await settleTaskFlow(input, operation);
|
|
76
|
+
const unbound = operation === 'flow-abandon';
|
|
77
|
+
const record = { flowId, status: termination, reason: reason(input.reason, operation), unbound, updatedAt: now };
|
|
78
|
+
const flows = [...authority.state.flows.filter(flow => flow.flowId !== flowId), record];
|
|
79
|
+
const event = { sequence: authority.state.sequence + 1, event: operation, flowId, at: now, reason: record.reason,
|
|
80
|
+
unbound: record.unbound, digest: digest(canonical({ operation, flowId, reason: record.reason, unbound: record.unbound })) };
|
|
81
|
+
const state = { ...authority.state, sequence: event.sequence, flows, records: [...authority.state.records, event] };
|
|
82
|
+
await save(authority.file, state);
|
|
83
|
+
return { schemaVersion: 'entrorove.local-execution/1.0', status: 'reported', operation, flowId,
|
|
84
|
+
termination, reason: record.reason, unbound: record.unbound,
|
|
85
|
+
...(settledTask?.task ? { taskId: settledTask.task.taskId, taskStatus: settledTask.task.status } : {}),
|
|
86
|
+
progress: state.records, nextStep: record.unbound
|
|
87
|
+
? { operation: 'start', instruction: 'The old local flow is released; start the original task again.' } : null };
|
|
88
|
+
}
|
|
89
|
+
export async function flowCancel(input) {
|
|
90
|
+
const place = await location(input); return withStateLock(place.file, () => runUnlocked('flow-cancel', input));
|
|
91
|
+
}
|
|
92
|
+
export async function flowAbandon(input) {
|
|
93
|
+
const place = await location(input); return withStateLock(place.file, () => runUnlocked('flow-abandon', input));
|
|
94
|
+
}
|
|
95
|
+
export const LOCAL_FLOW_LIFECYCLE_OPERATIONS = Object.freeze({ 'flow-cancel': flowCancel, 'flow-abandon': flowAbandon });
|
|
@@ -9,15 +9,20 @@ function literal(value, field) {
|
|
|
9
9
|
}
|
|
10
10
|
return value;
|
|
11
11
|
}
|
|
12
|
-
function within(directory, value, allowRoot = false) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
function within(directory, value, allowRoot = false, field = 'path') {
|
|
13
|
+
const raw = literal(value, field);
|
|
14
|
+
const resolved = path.posix.normalize(path.posix.join(directory, raw)).replace(/\/+$/, '');
|
|
15
|
+
const reason = resolved === '..' || resolved.startsWith('../') || resolved.startsWith('/')
|
|
16
|
+
? 'the resolved path is outside the repository root'
|
|
17
|
+
: resolved.split('/').some(part => ['.git', '.entrorove', '.aimlock', '.archguard'].includes(part))
|
|
18
|
+
? 'the resolved path enters a reserved control directory'
|
|
19
|
+
: null;
|
|
20
|
+
if (reason) {
|
|
21
|
+
throw new Error(`Scope tsconfig path escapes the authorized repository: ${JSON.stringify(raw)} resolves to ${JSON.stringify(resolved || '.')} from ${JSON.stringify(directory || '.')} because ${reason}`);
|
|
17
22
|
}
|
|
18
23
|
if (resolved === '.') {
|
|
19
24
|
if (!allowRoot)
|
|
20
|
-
throw new Error(
|
|
25
|
+
throw new Error(`Scope tsconfig mapping requires a literal candidate within the repository: ${JSON.stringify(raw)} resolves to the repository root from ${JSON.stringify(directory || '.')}`);
|
|
21
26
|
return '';
|
|
22
27
|
}
|
|
23
28
|
return resolved;
|
|
@@ -149,14 +154,14 @@ export async function scopeTsconfigImports(read, fromFile, configPath) {
|
|
|
149
154
|
const extended = literal(config.extends, 'extends');
|
|
150
155
|
if (!extended.startsWith('./') && !extended.startsWith('../'))
|
|
151
156
|
throw new Error('Scope tsconfig npm extends is unsupported; use an actual repository-relative extends');
|
|
152
|
-
inherited = await load(within(directory, extended), new Set([...ancestors, relative]), depth + 1);
|
|
157
|
+
inherited = await load(within(directory, extended, false, 'extends'), new Set([...ancestors, relative]), depth + 1);
|
|
153
158
|
}
|
|
154
159
|
const options = config.compilerOptions === undefined ? {} : config.compilerOptions;
|
|
155
160
|
if (!options || typeof options !== 'object' || Array.isArray(options))
|
|
156
161
|
throw new Error('Scope tsconfig compilerOptions must be an object');
|
|
157
162
|
const own = options;
|
|
158
163
|
return { ...inherited,
|
|
159
|
-
...(own.baseUrl === undefined ? {} : { baseUrl: within(directory,
|
|
164
|
+
...(own.baseUrl === undefined ? {} : { baseUrl: within(directory, own.baseUrl, true, 'baseUrl') }),
|
|
160
165
|
...(own.paths === undefined ? {} : { paths: pathsOption(own.paths), pathsOrigin: directory }) };
|
|
161
166
|
};
|
|
162
167
|
let directory = path.posix.dirname(canonical);
|
|
@@ -181,7 +186,7 @@ export async function scopeTsconfigImports(read, fromFile, configPath) {
|
|
|
181
186
|
if (resolution?.paths)
|
|
182
187
|
for (const targets of Object.values(resolution.paths))
|
|
183
188
|
for (const target of targets) {
|
|
184
|
-
within(resolution.baseUrl ?? resolution.pathsOrigin, target.replace('*', '__scope_wildcard__'));
|
|
189
|
+
within(resolution.baseUrl ?? resolution.pathsOrigin, target.replace('*', '__scope_wildcard__'), false, 'mapping');
|
|
185
190
|
}
|
|
186
191
|
return { candidates: specifier => importCandidates(resolution, specifier), mapped: specifier => {
|
|
187
192
|
if (typeof specifier !== 'string' || !specifier)
|
package/local-scope-files.mjs
CHANGED
|
@@ -23,9 +23,23 @@ export async function scopeRoot(value) {
|
|
|
23
23
|
if (!stat.isDirectory() || stat.isSymbolicLink()) scopeFail('ENTROROVE_ROOT_INVALID', 'repositoryRoot must be a real directory');
|
|
24
24
|
return fs.realpath(value);
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
function dependencyReference(value) { return value.split('/').includes('node_modules'); }
|
|
27
|
+
function pathSegments(value) { return value.split(path.sep).filter(Boolean); }
|
|
28
|
+
async function assertDependencyReference(root, target, relative) {
|
|
29
|
+
const resolved = await fs.realpath(target), rootReal = await fs.realpath(root);
|
|
30
|
+
const inside = resolved === rootReal || resolved.startsWith(`${rootReal}${path.sep}`);
|
|
31
|
+
const reserved = pathSegments(resolved).some(part => ['.git', '.entrorove', '.aimlock', '.archguard'].includes(part));
|
|
32
|
+
if (reserved || (!inside && !dependencyReference(resolved.split(path.sep).join('/')))) {
|
|
33
|
+
const reason = reserved ? 'the resolved target enters a reserved control directory'
|
|
34
|
+
: 'the resolved target is outside the authorized root and is not a dependency directory';
|
|
35
|
+
scopeFail('ENTROROVE_PATH_UNSAFE', `Scope dependency reference "${relative}" resolves to "${resolved}" because ${reason}`);
|
|
36
|
+
}
|
|
37
|
+
return resolved;
|
|
38
|
+
}
|
|
39
|
+
export async function scopeTarget(root, relative, allowMissing = false, allowDependency = false) {
|
|
27
40
|
let current = root;
|
|
28
41
|
const parts = scopePath(relative).split('/');
|
|
42
|
+
const followDependency = allowDependency && dependencyReference(relative);
|
|
29
43
|
for (const [index, part] of parts.entries()) {
|
|
30
44
|
current = path.join(current, part);
|
|
31
45
|
let stat;
|
|
@@ -33,30 +47,37 @@ export async function scopeTarget(root, relative, allowMissing = false) {
|
|
|
33
47
|
if (error.code === 'ENOENT' && allowMissing) return { target: path.join(root, relative), exists: false, stat: null };
|
|
34
48
|
throw error;
|
|
35
49
|
}
|
|
36
|
-
if (stat.isSymbolicLink()
|
|
37
|
-
|
|
50
|
+
if (stat.isSymbolicLink()) {
|
|
51
|
+
if (!followDependency) scopeFail('ENTROROVE_FILE_INVALID', 'scope refuses symlink or non-directory ancestors');
|
|
52
|
+
await assertDependencyReference(root, current, relative);
|
|
53
|
+
stat = await fs.stat(current);
|
|
54
|
+
}
|
|
55
|
+
if (index < parts.length - 1 && !stat.isDirectory()) scopeFail('ENTROROVE_FILE_INVALID', 'scope refuses symlink or non-directory ancestors');
|
|
56
|
+
if (index === parts.length - 1) return { target: current, exists: true, stat, dependency: followDependency };
|
|
38
57
|
}
|
|
39
58
|
}
|
|
40
|
-
async function safeScopeRead(root, relative, input) {
|
|
41
|
-
const target = await scopeTarget(root, relative);
|
|
59
|
+
async function safeScopeRead(root, relative, input, allowDependency = true) {
|
|
60
|
+
const dependency = allowDependency && dependencyReference(relative), target = await scopeTarget(root, relative, false, dependency);
|
|
42
61
|
if (!target.stat.isFile()) scopeFail('ENTROROVE_FILE_INVALID', 'scope source must be a regular file');
|
|
43
62
|
if (target.stat.size > 16_777_216) scopeFail('ENTROROVE_SCOPE_LIMIT', 'scope source exceeds the 16 MiB inspection bound');
|
|
44
|
-
const handle = await fs.open(target.target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
63
|
+
const handle = await fs.open(target.target, constants.O_RDONLY | (dependency ? 0 : (constants.O_NOFOLLOW ?? 0)));
|
|
45
64
|
try {
|
|
46
65
|
const before = await handle.stat();
|
|
66
|
+
const beforeReal = dependency ? await fs.realpath(target.target) : null;
|
|
47
67
|
const content = input ? await readBudgetedProjectFile(input, target.target, { sourcePath: relative }) : await handle.readFile();
|
|
48
68
|
const after = await handle.stat();
|
|
49
|
-
const latest = await fs.lstat(target.target);
|
|
69
|
+
const latest = dependency ? await fs.stat(target.target) : await fs.lstat(target.target);
|
|
70
|
+
const afterReal = dependency ? await fs.realpath(target.target) : null;
|
|
50
71
|
if (before.ino !== target.stat.ino || before.dev !== target.stat.dev || after.size !== before.size
|
|
51
72
|
|| after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || latest.ino !== after.ino
|
|
52
|
-
|| latest.dev !== after.dev || latest.isSymbolicLink()) scopeFail('ENTROROVE_SCOPE_CHANGED', 'scope source changed during inspection');
|
|
53
|
-
await scopeTarget(root, relative);
|
|
73
|
+
|| latest.dev !== after.dev || !dependency && latest.isSymbolicLink() || beforeReal !== afterReal) scopeFail('ENTROROVE_SCOPE_CHANGED', 'scope source changed during inspection');
|
|
74
|
+
await scopeTarget(root, relative, false, dependency);
|
|
54
75
|
return { content, mode: after.mode & 0o777, sha256: sha256(content), bytes: content.length };
|
|
55
76
|
} finally { await handle.close(); }
|
|
56
77
|
}
|
|
57
|
-
export const scopeRead = (root, relative, input = { repositoryRoot: root }) => safeScopeRead(root, relative, input);
|
|
78
|
+
export const scopeRead = (root, relative, input = { repositoryRoot: root }) => safeScopeRead(root, relative, input, true);
|
|
58
79
|
// Internal control roles only: signed manifest metadata and already validated private signer files.
|
|
59
|
-
export const scopeControlRead = (root, relative) => safeScopeRead(root, relative, null);
|
|
80
|
+
export const scopeControlRead = (root, relative) => safeScopeRead(root, relative, null, false);
|
|
60
81
|
const excluded = new Set(['.git', 'node_modules', '.entrorove', '.aimlock', '.archguard']);
|
|
61
82
|
export async function scopeFiles(root, requested, knownFiles = []) {
|
|
62
83
|
if (!Array.isArray(requested) || !requested.length) scopeFail('ENTROROVE_SCOPE_EMPTY', 'a non-empty target scope is required');
|
|
@@ -214,11 +214,12 @@ async function resolveImport(root, from, specifier, workspace, input, aliases, c
|
|
|
214
214
|
const relative = localPackage ?? path.posix.normalize(path.posix.join(path.posix.dirname(from), specifier));
|
|
215
215
|
return resolveLocalImport(root, relative);
|
|
216
216
|
}
|
|
217
|
+
const dependencyReference = relative => relative.split('/').includes('node_modules');
|
|
217
218
|
async function resolveLocalImport(root, relative, required = true) {
|
|
218
219
|
if (relative.startsWith('../') || relative === '..') scopeFail('ENTROROVE_PATH_UNSAFE', 'local import escapes the repository');
|
|
219
220
|
const replacements = /\.js$/.test(relative) ? [relative.replace(/\.js$/, '.ts'), relative.replace(/\.js$/, '.tsx')] : [];
|
|
220
221
|
for (const candidate of [relative, ...replacements, ...['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.vue', '.json', '.d.ts', '.d.mts', '.d.cts', '/index.js', '/index.ts', '/index.d.ts'].map(ext => relative + ext)]) {
|
|
221
|
-
const item = await scopeTarget(root, candidate, true);
|
|
222
|
+
const item = await scopeTarget(root, candidate, true, dependencyReference(candidate));
|
|
222
223
|
if (item.exists && item.stat.isFile()) return candidate;
|
|
223
224
|
}
|
|
224
225
|
if (required) scopeFail('ENTROROVE_IMPORT_UNRESOLVED', 'a local import has no resolvable source file');
|
package/local-task-routing.mjs
CHANGED
|
@@ -74,6 +74,9 @@ function taskRecord(state, taskId) {
|
|
|
74
74
|
if (!task) fail('ENTROROVE_TASK_UNKNOWN', `unknown task: ${taskId}`);
|
|
75
75
|
return task;
|
|
76
76
|
}
|
|
77
|
+
function taskFlowId(task) {
|
|
78
|
+
return task?.flowId ?? task?.checkpoint?.flowId ?? task?.route?.flowId ?? task?.acceptance?.flowId;
|
|
79
|
+
}
|
|
77
80
|
async function record(authority, event, data, nextState = authority.state) {
|
|
78
81
|
const entry = { sequence: authority.state.sequence + 1, event, at: new Date().toISOString(), ...data };
|
|
79
82
|
const state = { ...nextState, sequence: entry.sequence,
|
|
@@ -208,9 +211,33 @@ async function resumeUnlocked(input) {
|
|
|
208
211
|
}
|
|
209
212
|
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', ...recovered };
|
|
210
213
|
}
|
|
214
|
+
async function settleTaskFlowUnlocked(input, operation) {
|
|
215
|
+
if (!['flow-cancel', 'flow-abandon'].includes(operation)) fail('ENTROROVE_OPERATION_INVALID', 'Unknown flow lifecycle operation');
|
|
216
|
+
const flowId = requireId(input.flowId, 'flowId');
|
|
217
|
+
const authority = await load(input);
|
|
218
|
+
const taskId = input.taskId === undefined ? undefined : requireId(input.taskId, 'taskId');
|
|
219
|
+
const matches = authority.state.tasks.filter(task => (taskId ? task.taskId === taskId : true)
|
|
220
|
+
&& taskFlowId(task) === flowId);
|
|
221
|
+
if (!matches.length) return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', flowId, unbound: false, task: null };
|
|
222
|
+
const released = matches.map(task => {
|
|
223
|
+
if (operation === 'flow-abandon' && task.status === 'completed') fail('ENTROROVE_TASK_COMPLETED', 'Completed task cannot be abandoned');
|
|
224
|
+
if (operation === 'flow-cancel') return task.status === 'completed' ? task : { ...task, status: 'blocked', resumedAt: new Date().toISOString() };
|
|
225
|
+
const checkpoint = task.checkpoint && typeof task.checkpoint === 'object' && !Array.isArray(task.checkpoint)
|
|
226
|
+
? { ...task.checkpoint, flowId: null } : task.checkpoint;
|
|
227
|
+
return { ...task, status: 'pending', flowId: null, route: null, ...(checkpoint ? { checkpoint } : {}), resumedAt: new Date().toISOString() };
|
|
228
|
+
});
|
|
229
|
+
const releasedById = new Map(released.map(task => [task.taskId, task]));
|
|
230
|
+
const state = { ...authority.state, tasks: authority.state.tasks.map(task => releasedById.get(task.taskId) ?? task) };
|
|
231
|
+
const unbound = operation === 'flow-abandon';
|
|
232
|
+
const entry = await record(authority, unbound ? 'flow-unbind' : 'flow-cancel', { flowId, taskIds: released.map(task => task.taskId) }, state);
|
|
233
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', flowId, unbound,
|
|
234
|
+
task: released.length === 1 ? released[0] : undefined, taskIds: released.map(task => task.taskId), record: entry };
|
|
235
|
+
}
|
|
211
236
|
export async function taskDescribe(input) { return routingLock(input, () => describeUnlocked(input)); }
|
|
212
237
|
export async function messageRoute(input) { return routingLock(input, () => routeUnlocked(input)); }
|
|
213
238
|
export async function messageAccept(input) { return routingLock(input, () => acceptUnlocked(input)); }
|
|
214
239
|
export async function taskResume(input) { return resumeUnlocked(input); }
|
|
240
|
+
export async function settleTaskFlow(input, operation) { return routingLock(input, () => settleTaskFlowUnlocked(input, operation)); }
|
|
241
|
+
export async function releaseTaskFlow(input) { return settleTaskFlow(input, 'flow-abandon'); }
|
|
215
242
|
export const LOCAL_TASK_ROUTING_OPERATIONS = Object.freeze({ 'task-describe': taskDescribe, 'message-route': messageRoute, 'message-accept': messageAccept, 'task-resume': taskResume });
|
|
216
243
|
export const LOCAL_TASK_ROUTING_SCHEMAS = operationSchemasFor(LOCAL_TASK_ROUTING_OPERATIONS);
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"workflow-local-contracts.mjs",
|
|
66
66
|
"local-budget-renewal.mjs",
|
|
67
67
|
"local-probe.mjs",
|
|
68
|
+
"local-flow-lifecycle.mjs",
|
|
68
69
|
"local-task-routing.mjs",
|
|
69
70
|
"local-task-handoff-fence.mjs",
|
|
70
71
|
"local-task-handoff-storage.mjs",
|
|
@@ -203,7 +204,9 @@
|
|
|
203
204
|
"ide-hook.mjs",
|
|
204
205
|
"ide-install.mjs",
|
|
205
206
|
"ide-session.mjs",
|
|
207
|
+
"break-glass.mjs",
|
|
206
208
|
"ide-results.mjs",
|
|
209
|
+
"ide-queue.mjs",
|
|
207
210
|
"local-discipline-actions.mjs",
|
|
208
211
|
"local-discipline-analysis.mjs",
|
|
209
212
|
"local-discipline-batch.mjs",
|
package/release-state.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.40",
|
|
4
|
+
"digest": "4175f36123295cacefbcd09150116696d7528716ba1541256cf345ec3f31a2f3",
|
|
5
5
|
"files": {
|
|
6
6
|
"ide-code-mode.mjs": "cd5318342aac23ba9719ad760b7d7b8018d31839df1bed4ed7142c2782e22def",
|
|
7
|
-
"ide-command.mjs": "
|
|
8
|
-
"ide-hook.mjs": "
|
|
9
|
-
"ide-install.mjs": "
|
|
10
|
-
"ide-session.mjs": "
|
|
11
|
-
"
|
|
7
|
+
"ide-command.mjs": "e50a7e0b68bfc1812568f60562598a463ae169984a1abe9d401229c2b66c78c2",
|
|
8
|
+
"ide-hook.mjs": "567593a4c06911889954dc6499bd22be0ab67c78f0d8d46ad671bf55934f9041",
|
|
9
|
+
"ide-install.mjs": "65814d1e73cbdccdae4d8703cf9e8573492a3fd6a549ef8562a3f9d703f0ba09",
|
|
10
|
+
"ide-session.mjs": "964c5088454a9069fbf5672d2456ba136718c3b2e9eec31c96f7c5943972731b",
|
|
11
|
+
"break-glass.mjs": "2ad6f084c788d63fec11e64412864ac715bcb1a282dd2d8f03dc14e89e82135e",
|
|
12
|
+
"ide-results.mjs": "dfe0d3f773c65a9fc21056538d7005012c982ed4509bc0410d7f62813d0863fd",
|
|
13
|
+
"ide-queue.mjs": "806e716c775fd0079417027b135977c8516f47dddb0bc5ca19e32a355dab0e9e",
|
|
12
14
|
"local-discipline-actions.mjs": "2379c08d0f49c44068f8239db7e514d75885f4606b1b88a7a36e4ff19167caca",
|
|
13
15
|
"local-discipline-analysis.mjs": "ebea932438a53a157d5d07a5d68366496eee121a2c67cecfb4fdb2d5aa9763eb",
|
|
14
16
|
"local-discipline-batch.mjs": "2cc90dbae55efdee0c445da399c0a9acafa92e915344c25208ebca554fc4c9e9",
|
|
@@ -27,9 +29,9 @@
|
|
|
27
29
|
"runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
|
|
28
30
|
"snapshot-retention.mjs": "4f8919fe8ec49fb4c23e080a8ad2fa3a9282bca3fffe7cf55c64e89ef316697e",
|
|
29
31
|
"snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
|
|
30
|
-
"IDE-EXECUTION.md": "
|
|
32
|
+
"IDE-EXECUTION.md": "fe1c65819749fdd7cad4663eab7d11e6e566ef623dc30352e47effe0bd1263d9",
|
|
31
33
|
"REUSE-DISCIPLINE.md": "295ecd41c931b00c21d009ab65ac160f0e2ab81b4000ed498d2e8e97c2dc6c63",
|
|
32
|
-
"reuse-discipline-release.mjs": "
|
|
34
|
+
"reuse-discipline-release.mjs": "20ded9c21bd71b8779c454f5eba4c7c0a943ef99d94117b9bbef891d2ab6e3de",
|
|
33
35
|
"local-final-gate16.mjs": "b30caab08441449ad556a6e9a3cefbf91190f0b3f7370f46d2a21156375aa8e3",
|
|
34
36
|
"local-final-gate16-checks.mjs": "1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a",
|
|
35
37
|
"local-final-gate16-worker.mjs": "344d64b6c1c5552b96960e0d8514a876a3dbfa9762ab9eb749038e8351727121",
|
|
@@ -71,18 +73,18 @@
|
|
|
71
73
|
"local-autocoord17-waits.mjs": "2b0a713987822548a94e1ed3a9a891532db45383797dfc5d5bc8d1e35593cf25",
|
|
72
74
|
"local-validator-isolation17.mjs": "48e94604e28b8fddd567dfb35ff60b73fd094a5803bd49f076388b7f310f92aa",
|
|
73
75
|
"local-calctool-composition17.mjs": "ac89fcb41ab0d0d9b47c1b5696945281b0894eb064820775e36a011c76314e6a",
|
|
74
|
-
"cli.mjs": "
|
|
76
|
+
"cli.mjs": "bb99f5a52c770ded055fe27a35b3067ac348441b9488df336e6bf751b803e459",
|
|
75
77
|
"review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
|
|
76
78
|
"version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
|
|
77
|
-
"SKILL.md": "
|
|
78
|
-
"CAPABILITY-MAP.md": "
|
|
79
|
+
"SKILL.md": "d89c2a83a080a217d67d75fa0251b0f054b92e81a9e40a6c0b52c7ecf3d79099",
|
|
80
|
+
"CAPABILITY-MAP.md": "a0f0468a098725ced52a96dd8e22bfc7d1ce7681a8446ebf7dae3696c76c3385",
|
|
79
81
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
80
|
-
"package.json": "
|
|
82
|
+
"package.json": "a522540d3967d9348a37c6538c1abb39fe6d50872aa8f7f90b85e920590091c7",
|
|
81
83
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
82
|
-
"release.mjs": "
|
|
83
|
-
"local-cli.mjs": "
|
|
84
|
+
"release.mjs": "5a84da222a179cdc3b6be5db2a4a1c48085de27dff378b7598eb725f0627c98b",
|
|
85
|
+
"local-cli.mjs": "aa4c217c5029408020f76d6c0c5dafc9a0f51216085b2624f3b6aca3cffbe555",
|
|
84
86
|
"local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
|
|
85
|
-
"local-execution.mjs": "
|
|
87
|
+
"local-execution.mjs": "46fddd78cd954fb080c660affe90cdaa716695580427336acb820089441a8025",
|
|
86
88
|
"local-integrity.mjs": "e6d6eace80c610f3c7299d4f8644b0dc023aa9e3d8d64b26535ea7fbced248f1",
|
|
87
89
|
"local-evidence-verification.mjs": "9b7dea31777505272f63616fd62af7603338f703bb1285fc565d6451715c7ce0",
|
|
88
90
|
"local-formula.mjs": "706d5ec0cb2caf95a547531a81ca746fe246d4c53d902e03595a3b360e8c85af",
|
|
@@ -113,10 +115,11 @@
|
|
|
113
115
|
"local-budget-store.mjs": "2ae36c977ecd32d81f88e2eaa697ce8c228cb4405ecc5c4169cdda104c8ce138",
|
|
114
116
|
"local-budget-transition.mjs": "3894f05d4b5d4d6e16e3f201ff8902e6311cf6434010262934e0d1251aa8b9f0",
|
|
115
117
|
"local-budget.mjs": "e4ba1b4873a7625f9ff3c43e932b7289e7f95468c9dd88c2aeddd1242d7ea415",
|
|
118
|
+
"local-flow-lifecycle.mjs": "47534e16599002e5e7cf7d3e95195f4d7a364d2b651ec339d8c6c017d71c1b05",
|
|
116
119
|
"local-scope-candidate.mjs": "8f0bd9606e70d4a0ebacdd3cd600ec3afed85e2bb3e1760aa1144cf55d4e028a",
|
|
117
120
|
"local-scope-context.mjs": "79753abfc6c76089d17f003a1be537dfaea10d6731c542f7fc53f2a9996ed164",
|
|
118
|
-
"local-scope-files.mjs": "
|
|
119
|
-
"local-scope-repository.mjs": "
|
|
121
|
+
"local-scope-files.mjs": "96c301f2a45e5fa22f0b11fb15e5e564f118e53187fc7749263c78d5ed87b056",
|
|
122
|
+
"local-scope-repository.mjs": "0c955612d2f737e8661af84c1efb6e9e7fe6bb9ec1dfadd7776c61be177261f2",
|
|
120
123
|
"local-scope-git.mjs": "53bbbb6c4795db711b5bc164591a6ad79a561427da046d6bbe997dae6eb82829",
|
|
121
124
|
"local-scope-git-reads.mjs": "5d8a2636bab955cb325cb693db7423e69512355887ae5b5af3b8cb615f5ddad0",
|
|
122
125
|
"local-git-executable.mjs": "9391d716529025b1de3ee006df658a9d765b6ef29ad24ef9b7436829318e651e",
|
|
@@ -141,12 +144,12 @@
|
|
|
141
144
|
"runtime/git-native-linux-v1.mjs": "cf5cb91426a105e62c0f07a8a230dec592dba46f85a3f708b4c4e8cfcd1126c6",
|
|
142
145
|
"runtime/git-native-darwin-v1.mjs": "f88aab72dc86f94eb1115784872957cb8ed0f74205ae70e1ecd1939e7790e310",
|
|
143
146
|
"local-scope-signature.mjs": "bd05e8cf9e23f9511a16844974362297b9e9d11930ff7b8ad2a490e8d7660fe9",
|
|
144
|
-
"local-scope-config-imports.mjs": "
|
|
147
|
+
"local-scope-config-imports.mjs": "53e510609066355318d886cd96f73a0fc8005a28857f6ab4eb6cb81c4857945b",
|
|
145
148
|
"local-scope-snapshot.mjs": "c0f851470b98863a1d187825ed61ee6072d1c057b178fdc85ae29af02a3b250f",
|
|
146
149
|
"local-scope-state.mjs": "8b600a91fc0068d7a8c0bb752ddba7ed30ada93daafb118d6cba2368fd94cb27",
|
|
147
150
|
"local-scope-workspaces.mjs": "a44c34537e5ed2dcaf193a985e5bc05957989fbb160a650f9e37ba7d66eb2de8",
|
|
148
|
-
"local-task-routing.mjs": "
|
|
149
|
-
"remote-client.mjs": "
|
|
151
|
+
"local-task-routing.mjs": "6e2e506b2fb0e2e8d8c5147d7df32849b57a4e94a23634f01a355d45735858e4",
|
|
152
|
+
"remote-client.mjs": "9e4ef758fb7d83e30e82dca68623740e7848cd5a527b7a90846e125d00df0a12",
|
|
150
153
|
"cloud-task-client.mjs": "11acb1a033c94cdfbdfb177a3402ad97f6a5226f577138c8cf6395dc3ebb1a39",
|
|
151
154
|
"scope-runner-client.mjs": "646ab4f80d4a8209e922fd45f40efd968f0744d9dfc03188b222c1a2e11e83b8",
|
|
152
155
|
"native-client.mjs": "6f5575909bae9daba643287f876ffa0f9ff1c53a367b32a40145d740456d6605",
|
|
@@ -179,7 +182,7 @@
|
|
|
179
182
|
"workflow-local-request.mjs": "4e24c543ce386292a87dc9cd41f90d23375f12079cacffc515c8cd01fd571780",
|
|
180
183
|
"task-message-entry.mjs": "31d10056cb905237cd5649e8eb067009ff1612ec5dc1061e0373209fbc0b646a",
|
|
181
184
|
"task-message-cli.mjs": "3ca65e96c28129b3bd5cac962d106e0859a1f17df1f6a66ee543c6efc22f918d",
|
|
182
|
-
"local-contracts.mjs": "
|
|
185
|
+
"local-contracts.mjs": "d47022162233ee0b840bfd808b9d12590f4a367133ef0999233b7f76d76daa43",
|
|
183
186
|
"local-contract-types.mjs": "85e71fa4dd67e351a4b67573c740bcdb1661cede53f097189f11837771e75e91",
|
|
184
187
|
"local-archguard-ast.mjs": "7ff7df9dab0d98ed62983add751c80edea598bc94ef4f38ede0ebd4d41226c87",
|
|
185
188
|
"local-archguard-checkpoint.mjs": "37896902520cc995720c96288e4a5ed3585952b55c9c35be281f91fa754c71ec",
|
package/release.mjs
CHANGED
|
@@ -60,7 +60,7 @@ const sourceFiles = [
|
|
|
60
60
|
'local-budget-grants.mjs', 'local-budget-handoff.mjs', 'local-budget-handoff-contracts.mjs', 'local-budget-lock.mjs',
|
|
61
61
|
'local-budget-handoff-completion.mjs',
|
|
62
62
|
'local-budget-reads.mjs', 'local-budget-source-bindings.mjs', 'local-budget-store.mjs',
|
|
63
|
-
'local-budget-transition.mjs', 'local-budget.mjs',
|
|
63
|
+
'local-budget-transition.mjs', 'local-budget.mjs', 'local-flow-lifecycle.mjs',
|
|
64
64
|
'local-scope-candidate.mjs',
|
|
65
65
|
'local-scope-context.mjs',
|
|
66
66
|
'local-scope-files.mjs',
|
|
@@ -328,11 +328,13 @@ function releaseLabels() {
|
|
|
328
328
|
'local-budget-store.mjs': { zh: '预算追加台账和完整性校验', en: 'budget append-only journal and integrity checks' },
|
|
329
329
|
'local-budget-transition.mjs': { zh: '候选范围与正式范围预算转换', en: 'candidate and accepted scope budget transition' },
|
|
330
330
|
'local-budget.mjs': { zh: '本地三轴读取预算', en: 'local three-axis read budgets' },
|
|
331
|
+
'local-flow-lifecycle.mjs': { zh: 'Flow 终止与任务解绑', en: 'flow termination and task unbinding' },
|
|
331
332
|
'local-probe.mjs': { zh: '范围探测与分档', en: 'scope probing and tiering' },
|
|
332
333
|
'local-scope-candidate.mjs': { zh: '临时接管范围独立验签', en: 'temporary takeover scope verification' },
|
|
333
334
|
'local-scope-context.mjs': { zh: '精确符号范围绑定', en: 'exact symbol scope binding' },
|
|
334
335
|
'local-scope-files.mjs': { zh: '范围文件与安全读取', en: 'scope files and guarded reads' },
|
|
335
336
|
'local-scope-repository.mjs': { zh: '真实仓库范围事实', en: 'actual repository scope facts' },
|
|
337
|
+
'local-scope-config-imports.mjs': { zh: 'tsconfig 范围路径与依赖只读引用', en: 'tsconfig scope paths and read-only dependency references' },
|
|
336
338
|
'local-scope-git.mjs': { zh: '实际Git范围与变更事实', en: 'actual Git scope and change facts' },
|
|
337
339
|
'local-scope-git-reads.mjs': { zh: '固定Git元数据与源码读取', en: 'fixed Git metadata and source reads' },
|
|
338
340
|
'local-scope-git-comparison.mjs': { zh: 'Git范围比较接线', en: 'Git scope comparison wiring' },
|
package/releases.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.40",
|
|
4
|
+
"date": "2026-10-15",
|
|
5
|
+
"zh": "修复 flow 终止与会话解绑、tsconfig 授权路径解析,并加入离线 unlock 自救入口。",
|
|
6
|
+
"en": "Fixed flow termination and session unbinding, tsconfig authorized-path resolution, and added an offline unlock recovery command."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.1.39",
|
|
10
|
+
"date": "2026-10-14",
|
|
11
|
+
"zh": "增加受控需求队列,支持按优先级排队或在安全检查点插入;插入任务完成并回传真实确认后自动恢复原任务与原编号。",
|
|
12
|
+
"en": "Added a controlled requirement queue with priority ordering and checkpointed interruption; after verified acknowledged completion, the original task and identifiers resume automatically."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.1.38",
|
|
4
16
|
"date": "2026-10-14",
|
package/remote-client.mjs
CHANGED
|
@@ -34,7 +34,7 @@ function discoveredAjv() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export function buildInvocation(operation, input) {
|
|
37
|
-
if (!['capabilities', 'task-describe', 'message-route', 'message-accept', 'task-resume', 'task-checkpoint',
|
|
37
|
+
if (!['capabilities', 'task-describe', 'message-route', 'message-accept', 'task-resume', 'task-checkpoint', 'flow-cancel', 'flow-abandon',
|
|
38
38
|
'message-status', 'message-delivery-start', 'message-delivery-report', 'message-resolve', 'message-complete',
|
|
39
39
|
'handoff-prepare', 'handoff-release', 'handoff-claim', 'handoff-status', 'handoff-resume', 'handoff-execution-authority', 'mutation-snapshot-challenge', 'mutation-pass-issue',
|
|
40
40
|
'scope-runner-prepare', 'scope-runner-register', 'scope-runner-challenge', 'scope-runner-attest',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const ide = ['ide-code-mode.mjs', 'ide-command.mjs', 'ide-hook.mjs', 'ide-install.mjs', 'ide-session.mjs', 'ide-results.mjs'];
|
|
1
|
+
const ide = ['ide-code-mode.mjs', 'ide-command.mjs', 'ide-hook.mjs', 'ide-install.mjs', 'ide-session.mjs', 'break-glass.mjs', 'ide-results.mjs', 'ide-queue.mjs'];
|
|
2
2
|
const discipline = ['local-discipline-actions.mjs', 'local-discipline-analysis.mjs', 'local-discipline-batch.mjs',
|
|
3
3
|
'local-discipline-contracts.mjs', 'local-discipline-contracts.d.mts', 'local-discipline-gate.mjs',
|
|
4
4
|
'local-discipline-native.mjs', 'local-discipline-operations.mjs', 'local-discipline-repository.mjs',
|