@longtable/core 0.1.51 → 0.1.53
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/dist/hard-stop.d.ts +24 -0
- package/dist/hard-stop.js +243 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +10 -3
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HardStopScope, ResearchState } from "./types.js";
|
|
2
|
+
export type HardStopBlockerType = "question" | "obligation" | "spec_patch";
|
|
3
|
+
export interface HardStopBlocker {
|
|
4
|
+
type: HardStopBlockerType;
|
|
5
|
+
id: string;
|
|
6
|
+
scope: HardStopScope;
|
|
7
|
+
prompt: string;
|
|
8
|
+
reason: string;
|
|
9
|
+
source: string;
|
|
10
|
+
sourceField: string;
|
|
11
|
+
commandHint: string;
|
|
12
|
+
commandHints: string[];
|
|
13
|
+
nextAction: string;
|
|
14
|
+
priority: number;
|
|
15
|
+
}
|
|
16
|
+
export interface HardStopVerdict {
|
|
17
|
+
stopWouldBlock: boolean;
|
|
18
|
+
activeBlockers: HardStopBlocker[];
|
|
19
|
+
staleOrUnrelatedPendingQuestionCount: number;
|
|
20
|
+
stalePendingQuestionCount: number;
|
|
21
|
+
stalePendingObligationCount: number;
|
|
22
|
+
nextActions: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function collectHardStopBlockers(state: ResearchState): HardStopVerdict;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
const SCOPE_PRIORITY = {
|
|
2
|
+
research_question: 10,
|
|
3
|
+
scope: 20,
|
|
4
|
+
construct: 30,
|
|
5
|
+
method: 40,
|
|
6
|
+
evidence: 50,
|
|
7
|
+
protected_decision: 60
|
|
8
|
+
};
|
|
9
|
+
const CHECKPOINT_SCOPE_PATTERNS = [
|
|
10
|
+
[/research_(?:question|direction|specification)|research question|question_freeze|direction_change/i, "research_question"],
|
|
11
|
+
[/scope|boundary|criteria|required_sections|inclusion|exclusion/i, "scope"],
|
|
12
|
+
[/construct|theory|framing|ontology|measurement|validity|coding/i, "construct"],
|
|
13
|
+
[/method|analysis|sampling|design|plan|strategy/i, "method"],
|
|
14
|
+
[/evidence|access|source|corpus|full[-_ ]?text|pdf|scholarly/i, "evidence"],
|
|
15
|
+
[/protected_decision|protected decision|authorship_voice|epistemic/i, "protected_decision"]
|
|
16
|
+
];
|
|
17
|
+
const PATCH_SCOPE_PATTERNS = [
|
|
18
|
+
[/researchDirection\.question/i, "research_question"],
|
|
19
|
+
[/researchDirection|scope|inclusion|exclusion/i, "scope"],
|
|
20
|
+
[/constructOntology|theoryAndFraming|measurementCoding|coding/i, "construct"],
|
|
21
|
+
[/methodAnalysis|analysis|design/i, "method"],
|
|
22
|
+
[/evidenceAccess|source|corpus|access/i, "evidence"],
|
|
23
|
+
[/protectedDecisions|epistemicAlignment/i, "protected_decision"]
|
|
24
|
+
];
|
|
25
|
+
const PRODUCT_OR_TOOLING_PATTERN = /\b(product|runtime|guidance|setup|install|hook|mcp|codex|claude|skill|prompt|docs?|documentation|release|version|npm|git|github|simulation|policy|workflow|package|router|autocomplete)\b/i;
|
|
26
|
+
function compactText(...values) {
|
|
27
|
+
return values.filter(Boolean).join("\n");
|
|
28
|
+
}
|
|
29
|
+
function readBooleanFlag(value) {
|
|
30
|
+
return typeof value === "boolean" ? value : null;
|
|
31
|
+
}
|
|
32
|
+
function readScope(value) {
|
|
33
|
+
return value === "research_question" ||
|
|
34
|
+
value === "scope" ||
|
|
35
|
+
value === "construct" ||
|
|
36
|
+
value === "method" ||
|
|
37
|
+
value === "evidence" ||
|
|
38
|
+
value === "protected_decision"
|
|
39
|
+
? value
|
|
40
|
+
: null;
|
|
41
|
+
}
|
|
42
|
+
function scopeFromCommitmentFamily(family) {
|
|
43
|
+
switch (family) {
|
|
44
|
+
case "scope":
|
|
45
|
+
return "scope";
|
|
46
|
+
case "construct":
|
|
47
|
+
case "coding":
|
|
48
|
+
return "construct";
|
|
49
|
+
case "method":
|
|
50
|
+
return "method";
|
|
51
|
+
case "evidence":
|
|
52
|
+
return "evidence";
|
|
53
|
+
case "epistemic_authority":
|
|
54
|
+
return "protected_decision";
|
|
55
|
+
default:
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function scopeFromText(text) {
|
|
60
|
+
if (PRODUCT_OR_TOOLING_PATTERN.test(text)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return CHECKPOINT_SCOPE_PATTERNS.find(([pattern]) => pattern.test(text))?.[1] ?? null;
|
|
64
|
+
}
|
|
65
|
+
function questionSearchText(question) {
|
|
66
|
+
return compactText(question.prompt.checkpointKey, question.prompt.title, question.prompt.question, question.prompt.displayReason, question.commitmentFamily, question.epistemicBasis, ...(question.prompt.rationale ?? []));
|
|
67
|
+
}
|
|
68
|
+
function obligationSearchText(obligation) {
|
|
69
|
+
return compactText(obligation.kind, obligation.prompt, obligation.reason);
|
|
70
|
+
}
|
|
71
|
+
function inferQuestionHardStopScope(question) {
|
|
72
|
+
if (question.status !== "pending") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const explicitHardStop = readBooleanFlag(question.hardStop);
|
|
76
|
+
if (explicitHardStop === false) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const explicitScope = readScope(question.hardStopScope);
|
|
80
|
+
if (explicitHardStop === true) {
|
|
81
|
+
return {
|
|
82
|
+
scope: explicitScope ?? scopeFromCommitmentFamily(question.commitmentFamily) ?? scopeFromText(questionSearchText(question)) ?? "scope",
|
|
83
|
+
sourceField: explicitScope ? "QuestionRecord.hardStopScope" : "QuestionRecord.hardStop"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (!question.prompt.required) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const familyScope = scopeFromCommitmentFamily(question.commitmentFamily);
|
|
90
|
+
if (familyScope) {
|
|
91
|
+
return { scope: familyScope, sourceField: "QuestionRecord.commitmentFamily" };
|
|
92
|
+
}
|
|
93
|
+
const textScope = scopeFromText(questionSearchText(question));
|
|
94
|
+
return textScope ? { scope: textScope, sourceField: "QuestionRecord.derived" } : null;
|
|
95
|
+
}
|
|
96
|
+
function inferObligationHardStopScope(obligation, linkedQuestion) {
|
|
97
|
+
if (obligation.status !== "pending") {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const explicitHardStop = readBooleanFlag(obligation.hardStop);
|
|
101
|
+
if (explicitHardStop === false) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const explicitScope = readScope(obligation.hardStopScope);
|
|
105
|
+
if (explicitHardStop === true) {
|
|
106
|
+
return {
|
|
107
|
+
scope: explicitScope ?? (linkedQuestion ? inferQuestionHardStopScope(linkedQuestion)?.scope : null) ?? scopeFromText(obligationSearchText(obligation)) ?? "scope",
|
|
108
|
+
sourceField: explicitScope ? "LongTableQuestionObligation.hardStopScope" : "LongTableQuestionObligation.hardStop"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (obligation.kind === "research_specification_confirmation") {
|
|
112
|
+
return {
|
|
113
|
+
scope: scopeFromText(obligationSearchText(obligation)) ?? "scope",
|
|
114
|
+
sourceField: "LongTableQuestionObligation.derived"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (obligation.kind === "required_question" && linkedQuestion) {
|
|
118
|
+
const linkedScope = inferQuestionHardStopScope(linkedQuestion);
|
|
119
|
+
return linkedScope
|
|
120
|
+
? { scope: linkedScope.scope, sourceField: "LongTableQuestionObligation.derived" }
|
|
121
|
+
: null;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function scopeFromPatchChange(change) {
|
|
126
|
+
return PATCH_SCOPE_PATTERNS.find(([pattern]) => pattern.test(change.path))?.[1] ?? null;
|
|
127
|
+
}
|
|
128
|
+
function commandHintsForQuestion(id) {
|
|
129
|
+
return [
|
|
130
|
+
`longtable decide --question ${id} --answer <value> --rationale <why>`,
|
|
131
|
+
`longtable clear-question --question ${id} --reason <why safe to defer or clear>`
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
function commandHintsForBlocker(type, id, questionId) {
|
|
135
|
+
if (questionId) {
|
|
136
|
+
return commandHintsForQuestion(questionId);
|
|
137
|
+
}
|
|
138
|
+
if (type === "question") {
|
|
139
|
+
return commandHintsForQuestion(id);
|
|
140
|
+
}
|
|
141
|
+
if (type === "obligation") {
|
|
142
|
+
return [`longtable obligation resolve --id ${id} --reason <why>`];
|
|
143
|
+
}
|
|
144
|
+
return [`record a DecisionRecord before applying patch ${id}`];
|
|
145
|
+
}
|
|
146
|
+
function createBlocker(input) {
|
|
147
|
+
const commandHints = commandHintsForBlocker(input.type, input.id, input.questionId);
|
|
148
|
+
const commandHint = commandHints[0] ?? "decide, clear, or defer with rationale";
|
|
149
|
+
return {
|
|
150
|
+
type: input.type,
|
|
151
|
+
id: input.id,
|
|
152
|
+
scope: input.scope,
|
|
153
|
+
prompt: input.prompt,
|
|
154
|
+
reason: input.reason,
|
|
155
|
+
source: input.sourceField,
|
|
156
|
+
sourceField: input.sourceField,
|
|
157
|
+
commandHint,
|
|
158
|
+
commandHints,
|
|
159
|
+
nextAction: commandHint,
|
|
160
|
+
priority: SCOPE_PRIORITY[input.scope] + input.priorityOffset
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export function collectHardStopBlockers(state) {
|
|
164
|
+
const questions = state.questionLog ?? [];
|
|
165
|
+
const questionById = new Map(questions.map((question) => [question.id, question]));
|
|
166
|
+
const pendingQuestions = questions.filter((question) => question.status === "pending");
|
|
167
|
+
const pendingObligations = (state.questionObligations ?? []).filter((obligation) => obligation.status === "pending");
|
|
168
|
+
const blockers = [];
|
|
169
|
+
pendingQuestions.forEach((question, index) => {
|
|
170
|
+
const hardStop = inferQuestionHardStopScope(question);
|
|
171
|
+
if (!hardStop) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
blockers.push(createBlocker({
|
|
175
|
+
type: "question",
|
|
176
|
+
id: question.id,
|
|
177
|
+
scope: hardStop.scope,
|
|
178
|
+
prompt: question.prompt.question,
|
|
179
|
+
reason: question.prompt.displayReason ?? question.prompt.rationale?.[0] ?? "A Research Specification-affecting question is pending.",
|
|
180
|
+
sourceField: hardStop.sourceField,
|
|
181
|
+
priorityOffset: index / 1000
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
pendingObligations.forEach((obligation, index) => {
|
|
185
|
+
const linkedQuestion = obligation.questionId ? questionById.get(obligation.questionId) : undefined;
|
|
186
|
+
const hardStop = inferObligationHardStopScope(obligation, linkedQuestion);
|
|
187
|
+
if (!hardStop) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
blockers.push(createBlocker({
|
|
191
|
+
type: "obligation",
|
|
192
|
+
id: obligation.id,
|
|
193
|
+
scope: hardStop.scope,
|
|
194
|
+
prompt: obligation.prompt,
|
|
195
|
+
reason: obligation.reason,
|
|
196
|
+
sourceField: hardStop.sourceField,
|
|
197
|
+
questionId: obligation.questionId,
|
|
198
|
+
priorityOffset: 0.1 + index / 1000
|
|
199
|
+
}));
|
|
200
|
+
});
|
|
201
|
+
for (const [index, patch] of (state.specPatches ?? []).entries()) {
|
|
202
|
+
if (patch.status !== "proposed" || patch.decisionRecordId) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const scope = patch.changes.map(scopeFromPatchChange).find((candidate) => Boolean(candidate));
|
|
206
|
+
if (!scope) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
blockers.push(createBlocker({
|
|
210
|
+
type: "spec_patch",
|
|
211
|
+
id: patch.id,
|
|
212
|
+
scope,
|
|
213
|
+
prompt: patch.title,
|
|
214
|
+
reason: patch.rationale ?? "A proposed Research Specification patch changes protected research state.",
|
|
215
|
+
sourceField: "ResearchSpecificationPatch.changes",
|
|
216
|
+
questionId: patch.questionRecordId,
|
|
217
|
+
priorityOffset: 0.2 + index / 1000
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
const byKey = new Map();
|
|
221
|
+
for (const blocker of blockers) {
|
|
222
|
+
byKey.set(`${blocker.type}:${blocker.id}`, blocker);
|
|
223
|
+
}
|
|
224
|
+
const activeBlockers = [...byKey.values()].sort((left, right) => left.priority - right.priority ||
|
|
225
|
+
left.type.localeCompare(right.type) ||
|
|
226
|
+
left.id.localeCompare(right.id));
|
|
227
|
+
const blockerQuestionIds = new Set(activeBlockers.filter((blocker) => blocker.type === "question").map((blocker) => blocker.id));
|
|
228
|
+
const blockerObligationIds = new Set(activeBlockers.filter((blocker) => blocker.type === "obligation").map((blocker) => blocker.id));
|
|
229
|
+
const stalePendingQuestionCount = pendingQuestions.filter((question) => !blockerQuestionIds.has(question.id)).length;
|
|
230
|
+
const stalePendingObligationCount = pendingObligations.filter((obligation) => !blockerObligationIds.has(obligation.id)).length;
|
|
231
|
+
return {
|
|
232
|
+
stopWouldBlock: activeBlockers.length > 0,
|
|
233
|
+
activeBlockers,
|
|
234
|
+
staleOrUnrelatedPendingQuestionCount: stalePendingQuestionCount,
|
|
235
|
+
stalePendingQuestionCount,
|
|
236
|
+
stalePendingObligationCount,
|
|
237
|
+
nextActions: activeBlockers.length > 0
|
|
238
|
+
? activeBlockers.slice(0, 3).map((blocker) => blocker.commandHint)
|
|
239
|
+
: stalePendingQuestionCount > 0 || stalePendingObligationCount > 0
|
|
240
|
+
? ["Review stale non-hard-stop pending LongTable questions when convenient; they do not block Stop."]
|
|
241
|
+
: []
|
|
242
|
+
};
|
|
243
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export type NarrativeTraceVisibility = "explicit" | "inferred";
|
|
|
9
9
|
export type HypothesisStatus = "unconfirmed" | "confirmed" | "rejected";
|
|
10
10
|
export type ProviderKind = "claude" | "codex";
|
|
11
11
|
export type RoleKey = string;
|
|
12
|
-
export type InvocationKind = "single_role" | "panel" | "team" | "team_debate" | "status";
|
|
13
|
-
export type InvocationSurface = "native_parallel" | "native_subagents" | "generated_skill" | "prompt_alias" | "sequential_fallback" | "file_backed_debate" | "mcp_transport";
|
|
12
|
+
export type InvocationKind = "single_role" | "panel" | "panel_debate" | "team" | "team_debate" | "status";
|
|
13
|
+
export type InvocationSurface = "native_parallel" | "native_subagents" | "generated_skill" | "prompt_alias" | "sequential_fallback" | "file_backed_panel_debate" | "file_backed_debate" | "mcp_transport";
|
|
14
14
|
export type InvocationStatus = "planned" | "running" | "completed" | "blocked" | "degraded" | "error";
|
|
15
15
|
export type InteractionDepth = "independent" | "cross_reviewed" | "debated";
|
|
16
16
|
export type PanelVisibility = "synthesis_only" | "show_on_conflict" | "always_visible";
|
|
@@ -162,7 +162,7 @@ export interface TeamDebateRun {
|
|
|
162
162
|
status: InvocationStatus;
|
|
163
163
|
surface: InvocationSurface;
|
|
164
164
|
interactionDepth: InteractionDepth;
|
|
165
|
-
roundPolicy: "fixed" | "team_cross_review";
|
|
165
|
+
roundPolicy: "fixed" | "panel_cross_review" | "team_cross_review";
|
|
166
166
|
roundCount: number;
|
|
167
167
|
artifactRoot: string;
|
|
168
168
|
rounds: TeamDebateRound[];
|
|
@@ -191,6 +191,7 @@ export interface InferredHypothesis {
|
|
|
191
191
|
status: HypothesisStatus;
|
|
192
192
|
}
|
|
193
193
|
export type QuestionCommitmentFamily = "scope" | "construct" | "coding" | "method" | "evidence" | "epistemic_authority" | "product_policy";
|
|
194
|
+
export type QuestionHardStopScope = "research_question" | "scope" | "construct" | "method" | "evidence" | "protected_decision";
|
|
194
195
|
export type QuestionEpistemicBasis = "researcher_knowledge" | "project_state" | "external_evidence" | "ai_inference" | "mixed";
|
|
195
196
|
export interface DecisionRecord {
|
|
196
197
|
id: string;
|
|
@@ -323,6 +324,7 @@ export interface RoleAuditResult {
|
|
|
323
324
|
};
|
|
324
325
|
}
|
|
325
326
|
export type QuestionPromptType = "single_choice" | "multi_choice" | "free_text";
|
|
327
|
+
export type HardStopScope = QuestionHardStopScope;
|
|
326
328
|
export interface QuestionPrompt {
|
|
327
329
|
id: string;
|
|
328
330
|
checkpointKey?: string;
|
|
@@ -360,6 +362,8 @@ export interface QuestionRecord {
|
|
|
360
362
|
createdAt: string;
|
|
361
363
|
updatedAt: string;
|
|
362
364
|
status: QuestionRecordStatus;
|
|
365
|
+
hardStop?: boolean;
|
|
366
|
+
hardStopScope?: HardStopScope;
|
|
363
367
|
commitmentFamily?: QuestionCommitmentFamily;
|
|
364
368
|
epistemicBasis?: QuestionEpistemicBasis;
|
|
365
369
|
prompt: QuestionPrompt;
|
|
@@ -495,6 +499,7 @@ export interface LongTableHookRun {
|
|
|
495
499
|
}
|
|
496
500
|
export type LongTableQuestionObligationKind = "required_question" | "first_research_shape_confirmation" | "research_specification_confirmation";
|
|
497
501
|
export type LongTableQuestionObligationStatus = "pending" | "satisfied" | "cleared";
|
|
502
|
+
export type LongTableHardStopScope = HardStopScope;
|
|
498
503
|
export interface LongTableQuestionObligation {
|
|
499
504
|
id: string;
|
|
500
505
|
kind: LongTableQuestionObligationKind;
|
|
@@ -506,6 +511,8 @@ export interface LongTableQuestionObligation {
|
|
|
506
511
|
questionId?: string;
|
|
507
512
|
decisionId?: string;
|
|
508
513
|
sourceHookId?: string;
|
|
514
|
+
hardStop?: boolean;
|
|
515
|
+
hardStopScope?: HardStopScope;
|
|
509
516
|
}
|
|
510
517
|
export interface RuntimeGuidance {
|
|
511
518
|
mode: InteractionMode;
|