snipara-companion 3.2.29 → 3.2.30
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 +11 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +93 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for `snipara-companion`, newest first.
|
|
4
4
|
|
|
5
|
+
## New In 3.2.30
|
|
6
|
+
|
|
7
|
+
- Persists an explicit `--ack-review-only` as a 15-minute, one-use
|
|
8
|
+
acknowledgement bound to the exact guard profile, action, and finding
|
|
9
|
+
fingerprint, so the immediately following Git hook can consume it safely.
|
|
10
|
+
- Invalidates that acknowledgement when findings change and never applies it
|
|
11
|
+
to required acknowledgements, active conflicts, or hard blocks.
|
|
12
|
+
- Resolves collaboration state from `SNIPARA_WORKSPACE_DIR` or the Git
|
|
13
|
+
top-level directory, preventing filtered package commands from writing a
|
|
14
|
+
nested `packages/cli/.snipara/` session.
|
|
15
|
+
|
|
5
16
|
## New In 3.2.29
|
|
6
17
|
|
|
7
18
|
- Extends `workflow producer-report` with attributed gated-execution samples
|
package/dist/index.d.ts
CHANGED
|
@@ -2831,6 +2831,7 @@ declare function buildCodingIntelligenceLedger(options?: CodingLedgerBuildOption
|
|
|
2831
2831
|
declare function codingLedgerExportCommand(options: CodingLedgerExportCommandOptions): Promise<void>;
|
|
2832
2832
|
|
|
2833
2833
|
declare const COLLABORATION_STATE_RELATIVE_PATH: string;
|
|
2834
|
+
type CollaborationGuardProfile = "edit" | "pre-commit" | "pre-push" | "pre-deploy" | "migration" | "schema" | "release-package";
|
|
2834
2835
|
type CollaborationGuardActionKind = "safe_to_ack" | "needs_handoff" | "needs_test" | "needs_package_review" | "blocking_conflict" | "guard_unavailable";
|
|
2835
2836
|
interface CollaborationGuardActionCard {
|
|
2836
2837
|
kind: CollaborationGuardActionKind;
|
|
@@ -2910,6 +2911,13 @@ interface CollaborationLocalLeaseRecord {
|
|
|
2910
2911
|
expiresAt?: string | null;
|
|
2911
2912
|
workSessionId?: string;
|
|
2912
2913
|
}
|
|
2914
|
+
interface CollaborationReviewOnlyAcknowledgement {
|
|
2915
|
+
profile: CollaborationGuardProfile;
|
|
2916
|
+
action: string;
|
|
2917
|
+
findingFingerprint: string;
|
|
2918
|
+
acknowledgedAt: string;
|
|
2919
|
+
expiresAt: string;
|
|
2920
|
+
}
|
|
2913
2921
|
interface CollaborationLocalState {
|
|
2914
2922
|
schemaVersion: "snipara.collaboration.v1";
|
|
2915
2923
|
updatedAt: string;
|
|
@@ -2926,6 +2934,7 @@ interface CollaborationLocalState {
|
|
|
2926
2934
|
files: string[];
|
|
2927
2935
|
resources: CollaborationResource[];
|
|
2928
2936
|
leases: CollaborationLocalLeaseRecord[];
|
|
2937
|
+
reviewOnlyAcknowledgements: CollaborationReviewOnlyAcknowledgement[];
|
|
2929
2938
|
lastGuard?: {
|
|
2930
2939
|
decision: CollaborationGuardDecision;
|
|
2931
2940
|
severity: string;
|
package/dist/index.js
CHANGED
|
@@ -15400,6 +15400,7 @@ var DEFAULT_LOCAL_CODE_MAX_FILES = 2e3;
|
|
|
15400
15400
|
var HOSTED_GUARD_MAX_FILES = 450;
|
|
15401
15401
|
var HOSTED_GUARD_MAX_RESOURCES = 850;
|
|
15402
15402
|
var HOSTED_GUARD_MAX_SYMBOL_RESOURCES = 160;
|
|
15403
|
+
var REVIEW_ONLY_ACK_TTL_MS = 15 * 60 * 1e3;
|
|
15403
15404
|
var HOOK_BLOCK_PREFIX2 = "snipara:collaboration-guard";
|
|
15404
15405
|
var CHECKOUT_GIT_WRITE_RESOURCE = {
|
|
15405
15406
|
kind: "SURFACE",
|
|
@@ -15460,7 +15461,8 @@ function createEmptyCollaborationState(now = /* @__PURE__ */ new Date()) {
|
|
|
15460
15461
|
updatedAt: now.toISOString(),
|
|
15461
15462
|
files: [],
|
|
15462
15463
|
resources: [],
|
|
15463
|
-
leases: []
|
|
15464
|
+
leases: [],
|
|
15465
|
+
reviewOnlyAcknowledgements: []
|
|
15464
15466
|
};
|
|
15465
15467
|
}
|
|
15466
15468
|
function getCollaborationStatePath(rootDir = process.cwd()) {
|
|
@@ -15493,13 +15495,18 @@ function loadCollaborationState(rootDir = process.cwd()) {
|
|
|
15493
15495
|
files: normalizeFiles(parsed.files),
|
|
15494
15496
|
resources: normalizeResources(parsed.resources),
|
|
15495
15497
|
leases: normalizeLocalLeases(parsed.leases),
|
|
15498
|
+
reviewOnlyAcknowledgements: normalizeReviewOnlyAcknowledgements(
|
|
15499
|
+
parsed.reviewOnlyAcknowledgements
|
|
15500
|
+
),
|
|
15496
15501
|
lastGuard: normalizeLastGuard(parsed.lastGuard)
|
|
15497
15502
|
};
|
|
15498
15503
|
expireLocalLeases(state);
|
|
15504
|
+
expireReviewOnlyAcknowledgements(state);
|
|
15499
15505
|
return state;
|
|
15500
15506
|
}
|
|
15501
15507
|
function saveCollaborationState(state, rootDir = process.cwd()) {
|
|
15502
15508
|
expireLocalLeases(state);
|
|
15509
|
+
expireReviewOnlyAcknowledgements(state);
|
|
15503
15510
|
const statePath = getCollaborationStatePath(rootDir);
|
|
15504
15511
|
fs20.mkdirSync(path19.dirname(statePath), { recursive: true });
|
|
15505
15512
|
const tempPath = `${statePath}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -15814,13 +15821,14 @@ async function collaborationGuardCommand(options) {
|
|
|
15814
15821
|
const context = resolveCollaborationContext(options);
|
|
15815
15822
|
const state = loadCollaborationState(context.rootDir);
|
|
15816
15823
|
const profile = normalizeGuardProfile(options.profile) ?? "edit";
|
|
15824
|
+
const action = normalizeOptionalString3(options.action) ?? profile;
|
|
15817
15825
|
const files = resolveGuardFiles(context, options, profile);
|
|
15818
15826
|
const resources = resolveCommandResources(context, files, options, profile);
|
|
15819
15827
|
ensureFilesOrResources(files, resources, "guard");
|
|
15820
15828
|
const hostedPayload = buildHostedGuardPayload(files, resources);
|
|
15821
15829
|
const hosted = await maybeEvaluateHostedGuard(context, {
|
|
15822
15830
|
workSessionId: options.workSessionId ?? state.workSessionId,
|
|
15823
|
-
action
|
|
15831
|
+
action,
|
|
15824
15832
|
files: hostedPayload.files,
|
|
15825
15833
|
resources: hostedPayload.resources,
|
|
15826
15834
|
persist: options.persist !== false
|
|
@@ -15832,26 +15840,39 @@ async function collaborationGuardCommand(options) {
|
|
|
15832
15840
|
decision: evaluation.decision,
|
|
15833
15841
|
severity: evaluation.severity,
|
|
15834
15842
|
checkedAt: evaluation.evaluatedAt,
|
|
15835
|
-
action
|
|
15843
|
+
action,
|
|
15836
15844
|
resources: evaluation.resources,
|
|
15837
15845
|
conflictCount: evaluation.conflicts.length
|
|
15838
15846
|
};
|
|
15839
15847
|
state.files = mergeStrings(state.files, files);
|
|
15840
15848
|
state.resources = mergeResources(state.resources, evaluation.resources);
|
|
15849
|
+
}
|
|
15850
|
+
const explicitReviewOnlyAck = Boolean(options.enforce) && Boolean(options.ackReviewOnly) && isReviewOnlyGuardEvaluation(evaluation);
|
|
15851
|
+
const findingFingerprint = evaluation && isReviewOnlyGuardEvaluation(evaluation) ? fingerprintReviewOnlyGuardEvaluation(evaluation) : void 0;
|
|
15852
|
+
const persistedReviewOnlyAck = Boolean(options.enforce) && !options.ackReviewOnly && Boolean(
|
|
15853
|
+
findingFingerprint && consumeReviewOnlyAcknowledgement(state, profile, action, findingFingerprint)
|
|
15854
|
+
);
|
|
15855
|
+
if (explicitReviewOnlyAck && findingFingerprint) {
|
|
15856
|
+
persistReviewOnlyAcknowledgement(state, profile, action, findingFingerprint);
|
|
15857
|
+
} else if (evaluation && !persistedReviewOnlyAck) {
|
|
15858
|
+
invalidateReviewOnlyAcknowledgements(state, profile, action);
|
|
15859
|
+
}
|
|
15860
|
+
const reviewOnlyAcknowledged = explicitReviewOnlyAck || persistedReviewOnlyAck;
|
|
15861
|
+
const ackSource = explicitReviewOnlyAck ? "explicit" : persistedReviewOnlyAck ? "persisted" : "none";
|
|
15862
|
+
if (evaluation) {
|
|
15841
15863
|
saveCollaborationState(state, context.rootDir);
|
|
15842
15864
|
}
|
|
15843
|
-
const reviewOnlyAcknowledged = Boolean(options.enforce) && Boolean(options.ackReviewOnly) && isReviewOnlyGuardEvaluation(evaluation);
|
|
15844
15865
|
const actionCards = buildCollaborationGuardActionCards(evaluation, {
|
|
15845
15866
|
profile,
|
|
15846
15867
|
enforced: Boolean(options.enforce),
|
|
15847
|
-
ackReviewOnly:
|
|
15868
|
+
ackReviewOnly: reviewOnlyAcknowledged,
|
|
15848
15869
|
reviewOnlyAcknowledged
|
|
15849
15870
|
});
|
|
15850
15871
|
const guardFailed = shouldFailCollaborationGuard({
|
|
15851
15872
|
hostedStatus: hosted.status,
|
|
15852
15873
|
evaluation,
|
|
15853
15874
|
enforce: Boolean(options.enforce),
|
|
15854
|
-
ackReviewOnly:
|
|
15875
|
+
ackReviewOnly: reviewOnlyAcknowledged
|
|
15855
15876
|
});
|
|
15856
15877
|
if (guardFailed) {
|
|
15857
15878
|
process.exitCode = 2;
|
|
@@ -15867,6 +15888,7 @@ async function collaborationGuardCommand(options) {
|
|
|
15867
15888
|
enforced: Boolean(options.enforce),
|
|
15868
15889
|
ackReviewOnly: Boolean(options.ackReviewOnly),
|
|
15869
15890
|
reviewOnlyAcknowledged,
|
|
15891
|
+
ackSource,
|
|
15870
15892
|
failed: guardFailed
|
|
15871
15893
|
}
|
|
15872
15894
|
},
|
|
@@ -16036,7 +16058,8 @@ async function collaborationIdeStatusCommand(options) {
|
|
|
16036
16058
|
console.log(`Active leases: ${payload.hosted.activeLeases.length}`);
|
|
16037
16059
|
}
|
|
16038
16060
|
function resolveCollaborationContext(options) {
|
|
16039
|
-
const
|
|
16061
|
+
const requestedDir = normalizeOptionalString3(options.dir) ?? normalizeOptionalString3(process.env.SNIPARA_WORKSPACE_DIR) ?? process.cwd();
|
|
16062
|
+
const rootDir = resolveRepoRoot3(requestedDir);
|
|
16040
16063
|
const config = loadConfig({ cwd: rootDir });
|
|
16041
16064
|
const actor = buildCollaborationActor(options, config);
|
|
16042
16065
|
return {
|
|
@@ -17138,6 +17161,68 @@ function normalizeLocalLeases(value) {
|
|
|
17138
17161
|
return lease;
|
|
17139
17162
|
}).filter((item) => Boolean(item));
|
|
17140
17163
|
}
|
|
17164
|
+
function normalizeReviewOnlyAcknowledgements(value, now = /* @__PURE__ */ new Date()) {
|
|
17165
|
+
if (!Array.isArray(value)) {
|
|
17166
|
+
return [];
|
|
17167
|
+
}
|
|
17168
|
+
const nowMs = now.getTime();
|
|
17169
|
+
return value.map((item) => {
|
|
17170
|
+
const parsed = item;
|
|
17171
|
+
const profile = normalizeGuardProfile(parsed.profile);
|
|
17172
|
+
const action = normalizeOptionalString3(parsed.action);
|
|
17173
|
+
const findingFingerprint = normalizeOptionalString3(parsed.findingFingerprint);
|
|
17174
|
+
const acknowledgedAt = normalizeOptionalString3(parsed.acknowledgedAt);
|
|
17175
|
+
const expiresAt = normalizeOptionalString3(parsed.expiresAt);
|
|
17176
|
+
if (!profile || !action || !findingFingerprint || !acknowledgedAt || !expiresAt || !Number.isFinite(Date.parse(expiresAt)) || Date.parse(expiresAt) <= nowMs) {
|
|
17177
|
+
return null;
|
|
17178
|
+
}
|
|
17179
|
+
return { profile, action, findingFingerprint, acknowledgedAt, expiresAt };
|
|
17180
|
+
}).filter((item) => Boolean(item));
|
|
17181
|
+
}
|
|
17182
|
+
function expireReviewOnlyAcknowledgements(state, now = /* @__PURE__ */ new Date()) {
|
|
17183
|
+
state.reviewOnlyAcknowledgements = normalizeReviewOnlyAcknowledgements(
|
|
17184
|
+
state.reviewOnlyAcknowledgements,
|
|
17185
|
+
now
|
|
17186
|
+
);
|
|
17187
|
+
}
|
|
17188
|
+
function fingerprintReviewOnlyGuardEvaluation(evaluation) {
|
|
17189
|
+
const findings = evaluation.conflicts.map((conflict) => ({
|
|
17190
|
+
code: conflict.code,
|
|
17191
|
+
decision: conflict.decision,
|
|
17192
|
+
severity: conflict.severity,
|
|
17193
|
+
resource: `${conflict.resource.kind}:${conflict.resource.id}`,
|
|
17194
|
+
actor: conflict.conflictingActor ? `${conflict.conflictingActor.actorType}:${conflict.conflictingActor.actorId}` : null,
|
|
17195
|
+
workSessionId: conflict.workSessionId ?? null,
|
|
17196
|
+
reason: conflict.reason
|
|
17197
|
+
})).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
17198
|
+
return (0, import_node_crypto6.createHash)("sha256").update(JSON.stringify(findings)).digest("hex");
|
|
17199
|
+
}
|
|
17200
|
+
function persistReviewOnlyAcknowledgement(state, profile, action, findingFingerprint, now = /* @__PURE__ */ new Date()) {
|
|
17201
|
+
invalidateReviewOnlyAcknowledgements(state, profile, action);
|
|
17202
|
+
state.reviewOnlyAcknowledgements.push({
|
|
17203
|
+
profile,
|
|
17204
|
+
action,
|
|
17205
|
+
findingFingerprint,
|
|
17206
|
+
acknowledgedAt: now.toISOString(),
|
|
17207
|
+
expiresAt: new Date(now.getTime() + REVIEW_ONLY_ACK_TTL_MS).toISOString()
|
|
17208
|
+
});
|
|
17209
|
+
}
|
|
17210
|
+
function consumeReviewOnlyAcknowledgement(state, profile, action, findingFingerprint) {
|
|
17211
|
+
expireReviewOnlyAcknowledgements(state);
|
|
17212
|
+
const index = state.reviewOnlyAcknowledgements.findIndex(
|
|
17213
|
+
(ack) => ack.profile === profile && ack.action === action && ack.findingFingerprint === findingFingerprint
|
|
17214
|
+
);
|
|
17215
|
+
if (index < 0) {
|
|
17216
|
+
return false;
|
|
17217
|
+
}
|
|
17218
|
+
state.reviewOnlyAcknowledgements.splice(index, 1);
|
|
17219
|
+
return true;
|
|
17220
|
+
}
|
|
17221
|
+
function invalidateReviewOnlyAcknowledgements(state, profile, action) {
|
|
17222
|
+
state.reviewOnlyAcknowledgements = state.reviewOnlyAcknowledgements.filter(
|
|
17223
|
+
(ack) => ack.profile !== profile || ack.action !== action
|
|
17224
|
+
);
|
|
17225
|
+
}
|
|
17141
17226
|
function normalizeLastGuard(value) {
|
|
17142
17227
|
const parsed = value;
|
|
17143
17228
|
if (!parsed || !parsed.decision || !parsed.checkedAt) {
|
|
@@ -37497,7 +37582,7 @@ collaboration.command("guard").description("Check files or resources against act
|
|
|
37497
37582
|
"edit"
|
|
37498
37583
|
).option("-a, --action <action>", "Guarded action label", "edit").option("--actor <actor>", "Developer or agent display name").option("--actor-id <actorId>", "Stable developer or agent id").option("--actor-type <actorType>", "HUMAN|AGENT|SYSTEM", "AGENT").option("--session-id <sessionId>", "Automation/session id").option("--work-session-id <workSessionId>", "Existing hosted work session id").option("--client <client>", "Client label", "snipara-companion").option("--repository <repository>", "Repository id").option("-b, --branch <branch>", "Current branch name").option("--worktree <worktree>", "Worktree path").option("--max-files <number>", "Maximum files for local code resource expansion", "2000").option("--no-persist", "Evaluate without storing a guard event").option("--enforce", "Exit non-zero for REVIEW_REQUIRED or REQUIRES_ACK, not only BLOCKED").option(
|
|
37499
37584
|
"--ack-review-only",
|
|
37500
|
-
"Under --enforce,
|
|
37585
|
+
"Under --enforce, persist one exact review-only acknowledgement for the next hook rerun"
|
|
37501
37586
|
).option("-d, --dir <directory>", "Repository directory (default: current)").option("--json", "Print raw JSON").option("--verbose", "Print the full guard report even when the guard passes").action(async (options) => {
|
|
37502
37587
|
await collaborationGuardCommand({
|
|
37503
37588
|
files: options.files,
|