@besales/ops-framework 0.1.14 → 0.1.16
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 +10 -0
- package/bin/initiative.mjs +87 -6
- package/bin/initiative.test.mjs +148 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.16
|
|
4
|
+
|
|
5
|
+
- Added a schema compatibility readiness gate that blocks planning when a closed/minimal schema requirement does not support dependent approved requirements such as speaker resolution, aliases and meeting track classification.
|
|
6
|
+
- Added a schema attribution/access warning when sources mention `created_by`, `app.current_user_id`, `owner_id`, `entity_history` or deferred enforcement but the schema requirement omits them.
|
|
7
|
+
|
|
8
|
+
## 0.1.15
|
|
9
|
+
|
|
10
|
+
- Strengthened the initiative track-scope/storage synthesis gate to detect wide meeting tracks routed to commitments/decisions/risks via `related_person_id` when that storage is deferred.
|
|
11
|
+
- Linked the track-scope gate action to the apply-contract decision so synthesis resolves both scope and write semantics together.
|
|
12
|
+
|
|
3
13
|
## 0.1.14
|
|
4
14
|
|
|
5
15
|
- Added initiative synthesis readiness gates for missing critical concepts, ADR/NFR coverage, scope/storage conflicts, apply-contract ambiguity, sequencing hints and stronger source-reference warnings.
|
package/bin/initiative.mjs
CHANGED
|
@@ -1035,6 +1035,8 @@ function buildSynthesisReadinessGates({ requirements, sourceDocs }) {
|
|
|
1035
1035
|
});
|
|
1036
1036
|
addScopeStorageConflictGate({ gates, sourceText, requirementText });
|
|
1037
1037
|
addApplyContractGate({ gates, sourceText, requirementText });
|
|
1038
|
+
addSchemaCompatibilityGate({ gates, sourceText, requirements });
|
|
1039
|
+
addSchemaAttributionWarningGate({ gates, sourceText, requirements });
|
|
1038
1040
|
addSequencingGate({ gates, sourceText, requirementText });
|
|
1039
1041
|
addStrongerSourceRefGate({ gates, sourceDocs, requirements });
|
|
1040
1042
|
return gates;
|
|
@@ -1064,18 +1066,21 @@ function addMissingConceptGate({
|
|
|
1064
1066
|
}
|
|
1065
1067
|
|
|
1066
1068
|
function addScopeStorageConflictGate({ gates, sourceText, requirementText }) {
|
|
1067
|
-
const
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1069
|
+
const combinedText = `${requirementText}\n${sourceText}`;
|
|
1070
|
+
const mentionsWideTrackTaxonomy = /(tracker|advisor[_ -]?network|advisor-network|investor|grant[_ -]?funding|fundraising|grants?)/i.test(combinedText);
|
|
1071
|
+
const mapsWideTracksToFactStorage = /(advisor[_ -]?network|advisor-network|fundraising|investor|grant[_ -]?funding|grants?|tracker).{0,240}(commitments?|decisions?|risks?|related_person_id|nullable client_id|meeting_track\s+in)/is.test(sourceText)
|
|
1072
|
+
|| /(commitments?|decisions?|risks?|related_person_id|nullable client_id|meeting_track\s+in).{0,240}(advisor[_ -]?network|advisor-network|fundraising|investor|grant[_ -]?funding|grants?|tracker)/is.test(sourceText);
|
|
1073
|
+
const defersFactStorage = /(commitments?|decisions?|risks?|related_person_id).{0,160}(phase\s*2|future|defer|deferred|отлож)/is.test(combinedText)
|
|
1074
|
+
|| /(phase\s*2|future|defer|deferred|отлож).{0,160}(commitments?|decisions?|risks?|related_person_id)/is.test(combinedText);
|
|
1070
1075
|
const hasExplicitTrackScopeDecision = /(client\s*\+\s*internal|client and internal|только client|только клиент|only client|track[- ]scope|6 tracks?.{0,80}(phase\s*2|future|defer))/i.test(requirementText);
|
|
1071
|
-
if (mentionsWideTrackTaxonomy && defersFactStorage && !hasExplicitTrackScopeDecision) {
|
|
1076
|
+
if (mentionsWideTrackTaxonomy && mapsWideTracksToFactStorage && defersFactStorage && !hasExplicitTrackScopeDecision) {
|
|
1072
1077
|
gates.push({
|
|
1073
1078
|
id: 'scope-storage-track-conflict',
|
|
1074
1079
|
severity: 'needs_decision',
|
|
1075
1080
|
blocking: true,
|
|
1076
1081
|
title: 'Track taxonomy conflicts with deferred fact storage',
|
|
1077
|
-
issue: '
|
|
1078
|
-
action: 'Decide explicitly: Phase 1
|
|
1082
|
+
issue: 'Sources connect non-client/internal meeting tracks to commitments/decisions/risks or related-person facts while that storage appears deferred.',
|
|
1083
|
+
action: 'Decide explicitly with the apply-contract gate: Phase 1 extraction is client+internal only, or Phase 1 includes minimal commitments/decisions/risks storage with related_person_id for the wider track set.',
|
|
1079
1084
|
});
|
|
1080
1085
|
}
|
|
1081
1086
|
}
|
|
@@ -1097,6 +1102,82 @@ function addApplyContractGate({ gates, sourceText, requirementText }) {
|
|
|
1097
1102
|
}
|
|
1098
1103
|
}
|
|
1099
1104
|
|
|
1105
|
+
function addSchemaCompatibilityGate({ gates, sourceText, requirements }) {
|
|
1106
|
+
const schemaReq = findSchemaRequirement(requirements);
|
|
1107
|
+
if (!schemaReq) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const schemaText = requirementFullText(schemaReq);
|
|
1111
|
+
const allRequirementText = requirements.map((req) => requirementFullText(req)).join('\n');
|
|
1112
|
+
const schemaLooksClosed = /(includes?\s+only|only\s+includes?|schema\s+includes\s+only|только|исключительно)/i.test(schemaText);
|
|
1113
|
+
const hasSpeakerResolutionRequirement = /(speaker[_ -]?resolution|speaker identity|raw speaker|raw_speaker|person_aliases|aliases?|alias-хранилище)/i.test(allRequirementText);
|
|
1114
|
+
const schemaSupportsSpeakerResolution = /(person_aliases|raw_speaker_labels?|speaker_resolution_status|speaker alias|speaker label|aliases?)/i.test(schemaText);
|
|
1115
|
+
const hasTrackClassificationRequirement = /(meeting_track|track classification|classif(?:y|ication).{0,80}track|классификац.{0,80}трек)/i.test(`${allRequirementText}\n${sourceText}`);
|
|
1116
|
+
const schemaSupportsTrackClassification = /meeting_track/i.test(schemaText);
|
|
1117
|
+
const sourceMentionsMeetingOperationalFields = /(series_id|asr_quality)/i.test(sourceText);
|
|
1118
|
+
const schemaSupportsMeetingOperationalFields = !sourceMentionsMeetingOperationalFields || /(series_id|asr_quality)/i.test(schemaText);
|
|
1119
|
+
const missing = [];
|
|
1120
|
+
if (hasSpeakerResolutionRequirement && !schemaSupportsSpeakerResolution) {
|
|
1121
|
+
missing.push('speaker resolution storage (`person_aliases` or `raw_speaker_labels` + `speaker_resolution_status`)');
|
|
1122
|
+
}
|
|
1123
|
+
if (hasTrackClassificationRequirement && !schemaSupportsTrackClassification) {
|
|
1124
|
+
missing.push('`meeting_track` on meetings');
|
|
1125
|
+
}
|
|
1126
|
+
if (!schemaSupportsMeetingOperationalFields) {
|
|
1127
|
+
missing.push('meeting operational fields mentioned by sources, such as `series_id` and `asr_quality`');
|
|
1128
|
+
}
|
|
1129
|
+
if (missing.length && schemaLooksClosed) {
|
|
1130
|
+
gates.push({
|
|
1131
|
+
id: 'schema-supports-derived-requirements',
|
|
1132
|
+
severity: 'critical',
|
|
1133
|
+
blocking: true,
|
|
1134
|
+
title: 'Schema requirement does not support dependent requirements',
|
|
1135
|
+
issue: `Schema requirement ${schemaReq.id} is written as a closed/minimal schema list but omits ${missing.join(', ')}.`,
|
|
1136
|
+
action: 'Update the schema requirement before WP planning: add the missing schema support, or explicitly state the alternative Phase 1 storage contract and defer the dependent behavior.',
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function addSchemaAttributionWarningGate({ gates, sourceText, requirements }) {
|
|
1142
|
+
const schemaReq = findSchemaRequirement(requirements);
|
|
1143
|
+
if (!schemaReq) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const schemaText = requirementFullText(schemaReq);
|
|
1147
|
+
const sourceMentionsAttribution = /(created_by|app\.current_user_id|owner_id|entity_history|enforcement[- ]deferred)/i.test(sourceText);
|
|
1148
|
+
const missing = [
|
|
1149
|
+
['created_by', /created_by/i],
|
|
1150
|
+
['app.current_user_id', /app\.current_user_id/i],
|
|
1151
|
+
['owner_id', /owner_id/i],
|
|
1152
|
+
['entity_history', /entity_history/i],
|
|
1153
|
+
['enforcement-deferred', /enforcement[- ]deferred/i],
|
|
1154
|
+
].filter(([, pattern]) => !pattern.test(schemaText)).map(([name]) => name);
|
|
1155
|
+
if (sourceMentionsAttribution && missing.length) {
|
|
1156
|
+
gates.push({
|
|
1157
|
+
id: 'schema-attribution-nfr-warning',
|
|
1158
|
+
severity: 'planning_hint',
|
|
1159
|
+
blocking: false,
|
|
1160
|
+
title: 'Schema attribution/access fields are not explicit',
|
|
1161
|
+
issue: `Sources mention attribution/access schema decisions, but schema requirement ${schemaReq.id} omits ${missing.join(', ')}.`,
|
|
1162
|
+
action: 'Before WP-001, add acceptance for created_by/app.current_user_id, owner_id, entity_history and deferred role enforcement if these are Phase 1 schema foundations.',
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function findSchemaRequirement(requirements) {
|
|
1168
|
+
return requirements.find((req) => /(schema|migration|tables?|таблиц|схем)/i.test(requirementFullText(req)));
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function requirementFullText(req) {
|
|
1172
|
+
return [
|
|
1173
|
+
req.id,
|
|
1174
|
+
req.text,
|
|
1175
|
+
req.acceptance,
|
|
1176
|
+
req.notes,
|
|
1177
|
+
req.workPackage,
|
|
1178
|
+
].filter(Boolean).join('\n');
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1100
1181
|
function addSequencingGate({ gates, sourceText, requirementText }) {
|
|
1101
1182
|
const hasGoldenSet = /(golden set|golden-set|regression baseline|baseline regression)/i.test(`${sourceText}\n${requirementText}`);
|
|
1102
1183
|
const hasMeetingExtraction = /(process-meeting|process meeting|\/process-meeting|meeting extraction)/i.test(requirementText);
|
package/bin/initiative.test.mjs
CHANGED
|
@@ -388,6 +388,7 @@ describe('initiative framework', () => {
|
|
|
388
388
|
'- Golden set baseline Week 1-2 for meeting extraction regression.',
|
|
389
389
|
'- Migration 001b: person_aliases. Meeting fields: meeting_track, series_id, asr_quality, speaker_resolution_status.',
|
|
390
390
|
'- Analysis found single-label exports, alias zoo, committed_by_person attribution risk.',
|
|
391
|
+
'- advisor_network, fundraising and grants are routed into commitments, decisions and risks by meeting_track.',
|
|
391
392
|
'- Phase 2 defers commitments decisions risks related_person_id storage.',
|
|
392
393
|
].join('\n'));
|
|
393
394
|
fs.writeFileSync(path.join(docsDir, '07-adrs.md'), [
|
|
@@ -445,6 +446,153 @@ describe('initiative framework', () => {
|
|
|
445
446
|
expect(pack).toContain('Golden set should baseline meeting extraction');
|
|
446
447
|
expect(pack).toContain('Stronger source reference available');
|
|
447
448
|
});
|
|
449
|
+
|
|
450
|
+
it('detects track scope storage conflicts when routing evidence is only in source docs', () => {
|
|
451
|
+
const root = makeProject();
|
|
452
|
+
const docsDir = path.join(root, 'docs', 'delivery-os');
|
|
453
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
454
|
+
fs.writeFileSync(path.join(docsDir, '06-roadmap.md'), [
|
|
455
|
+
'# Phase 1',
|
|
456
|
+
'',
|
|
457
|
+
'- MVP taxonomy must cover client, internal, tracker, advisor_network, investor, grant_funding.',
|
|
458
|
+
'- Minimal Phase 1 schema defers commitments, decisions, risks and related_person_id storage to Phase 2.',
|
|
459
|
+
].join('\n'));
|
|
460
|
+
fs.writeFileSync(path.join(docsDir, '12-para-workspace-contract.md'), [
|
|
461
|
+
'# Workspace Contract',
|
|
462
|
+
'',
|
|
463
|
+
'- advisor-network / fundraising / grants are routed into commitments, decisions and risks where meeting_track IN (...).',
|
|
464
|
+
].join('\n'));
|
|
465
|
+
fs.writeFileSync(path.join(docsDir, '13-meeting-extraction-profiles.md'), [
|
|
466
|
+
'# Profiles',
|
|
467
|
+
'',
|
|
468
|
+
'- Non-client tracks require nullable client_id plus related_person_id on commitments, decisions and risks.',
|
|
469
|
+
].join('\n'));
|
|
470
|
+
createInitiative({
|
|
471
|
+
projectRoot: root,
|
|
472
|
+
initiativeId: 'delivery-os-mvp',
|
|
473
|
+
title: 'Delivery OS MVP',
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
initiativeIntake({
|
|
477
|
+
projectRoot: root,
|
|
478
|
+
initiativeId: 'delivery-os-mvp',
|
|
479
|
+
docsDir: 'docs/delivery-os',
|
|
480
|
+
phase: 'phase-1',
|
|
481
|
+
});
|
|
482
|
+
initiativeRequirements({
|
|
483
|
+
projectRoot: root,
|
|
484
|
+
initiativeId: 'delivery-os-mvp',
|
|
485
|
+
phase: 'phase-1',
|
|
486
|
+
});
|
|
487
|
+
const synthesis = initiativeRequirementsSynthesis({
|
|
488
|
+
projectRoot: root,
|
|
489
|
+
initiativeId: 'delivery-os-mvp',
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const gate = synthesis.gates.find((item) => item.id === 'scope-storage-track-conflict');
|
|
493
|
+
expect(gate).toMatchObject({
|
|
494
|
+
severity: 'needs_decision',
|
|
495
|
+
blocking: true,
|
|
496
|
+
});
|
|
497
|
+
expect(gate.action).toContain('client+internal');
|
|
498
|
+
expect(gate.action).toContain('related_person_id');
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('blocks planning when closed schema requirements do not support dependent approved requirements', () => {
|
|
502
|
+
const root = makeProject();
|
|
503
|
+
const docsDir = path.join(root, 'docs', 'delivery-os');
|
|
504
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
505
|
+
fs.writeFileSync(path.join(docsDir, '06-roadmap.md'), [
|
|
506
|
+
'# Phase 1',
|
|
507
|
+
'',
|
|
508
|
+
'- Migration 001b: person_aliases. Meeting fields: meeting_track, series_id, asr_quality, speaker_resolution_status.',
|
|
509
|
+
].join('\n'));
|
|
510
|
+
fs.writeFileSync(path.join(docsDir, '07-adrs.md'), [
|
|
511
|
+
'# ADR-6',
|
|
512
|
+
'',
|
|
513
|
+
'- Schema-ready enforcement-deferred: created_by, app.current_user_id, owner_id, entity_history.',
|
|
514
|
+
].join('\n'));
|
|
515
|
+
createInitiative({
|
|
516
|
+
projectRoot: root,
|
|
517
|
+
initiativeId: 'delivery-os-mvp',
|
|
518
|
+
title: 'Delivery OS MVP',
|
|
519
|
+
});
|
|
520
|
+
initiativeIntake({
|
|
521
|
+
projectRoot: root,
|
|
522
|
+
initiativeId: 'delivery-os-mvp',
|
|
523
|
+
docsDir: 'docs/delivery-os',
|
|
524
|
+
phase: 'phase-1',
|
|
525
|
+
});
|
|
526
|
+
const initiativeDir = path.join(root, 'ops', 'agent-pipeline', 'initiatives', 'delivery-os-mvp');
|
|
527
|
+
fs.writeFileSync(path.join(initiativeDir, 'requirements-map.md'), [
|
|
528
|
+
'# Requirements Map',
|
|
529
|
+
'',
|
|
530
|
+
'## Requirement Details',
|
|
531
|
+
'',
|
|
532
|
+
'### REQ-002',
|
|
533
|
+
'',
|
|
534
|
+
'- Status: `approved`',
|
|
535
|
+
'- Source: `docs/delivery-os/04-data-model.md:1206`',
|
|
536
|
+
'- Source hash: `sha`',
|
|
537
|
+
'- Phase: `phase-1`',
|
|
538
|
+
'- Quality: `complete`',
|
|
539
|
+
'- Quality issue: (none)',
|
|
540
|
+
'- Work package: WP-001',
|
|
541
|
+
'- Acceptance: Schema includes only team_members, clients, people, meetings, evidence, change_groups, proposed_changes, skill_executions, entity_history.',
|
|
542
|
+
'- Decision: `approve`',
|
|
543
|
+
'- Notes: Human-approved minimal schema.',
|
|
544
|
+
'- Requirement: Minimal Phase 1 schema includes only team_members, clients, people, meetings, evidence, change_groups, proposed_changes, skill_executions, entity_history.',
|
|
545
|
+
'',
|
|
546
|
+
'### REQ-003',
|
|
547
|
+
'',
|
|
548
|
+
'- Status: `approved`',
|
|
549
|
+
'- Source: `docs/delivery-os/06-roadmap.md:10`',
|
|
550
|
+
'- Source hash: `sha`',
|
|
551
|
+
'- Phase: `phase-1`',
|
|
552
|
+
'- Quality: `complete`',
|
|
553
|
+
'- Quality issue: (none)',
|
|
554
|
+
'- Work package: WP-003',
|
|
555
|
+
'- Acceptance: Meetings are classified with `meeting_track`.',
|
|
556
|
+
'- Decision: `approve`',
|
|
557
|
+
'- Notes: (empty)',
|
|
558
|
+
'- Requirement: Phase 1 classifies meetings by track using meeting_track.',
|
|
559
|
+
'',
|
|
560
|
+
'### REQ-004',
|
|
561
|
+
'',
|
|
562
|
+
'- Status: `approved`',
|
|
563
|
+
'- Source: `docs/delivery-os/06-roadmap.md:11`',
|
|
564
|
+
'- Source hash: `sha`',
|
|
565
|
+
'- Phase: `phase-1`',
|
|
566
|
+
'- Quality: `complete`',
|
|
567
|
+
'- Quality issue: (none)',
|
|
568
|
+
'- Work package: WP-001, WP-003',
|
|
569
|
+
'- Acceptance: Speaker resolution stores raw speaker labels and aliases and does not attribute unresolved aliases automatically.',
|
|
570
|
+
'- Decision: `approve`',
|
|
571
|
+
'- Notes: (empty)',
|
|
572
|
+
'- Requirement: Phase 1 supports speaker identity resolution by storing raw speaker labels and aliases.',
|
|
573
|
+
'',
|
|
574
|
+
].join('\n'));
|
|
575
|
+
|
|
576
|
+
const synthesis = initiativeRequirementsSynthesis({
|
|
577
|
+
projectRoot: root,
|
|
578
|
+
initiativeId: 'delivery-os-mvp',
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const schemaGate = synthesis.gates.find((gate) => gate.id === 'schema-supports-derived-requirements');
|
|
582
|
+
const attributionGate = synthesis.gates.find((gate) => gate.id === 'schema-attribution-nfr-warning');
|
|
583
|
+
expect(schemaGate).toMatchObject({
|
|
584
|
+
severity: 'critical',
|
|
585
|
+
blocking: true,
|
|
586
|
+
});
|
|
587
|
+
expect(schemaGate.issue).toContain('person_aliases');
|
|
588
|
+
expect(schemaGate.issue).toContain('meeting_track');
|
|
589
|
+
expect(schemaGate.issue).toContain('series_id');
|
|
590
|
+
expect(attributionGate).toMatchObject({
|
|
591
|
+
severity: 'planning_hint',
|
|
592
|
+
blocking: false,
|
|
593
|
+
});
|
|
594
|
+
expect(synthesis.rewriteRequired).toBe(true);
|
|
595
|
+
});
|
|
448
596
|
});
|
|
449
597
|
|
|
450
598
|
function makeProject() {
|