@besales/ops-framework 0.1.3 → 0.1.4

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4
4
+
5
+ - Switched generated project scripts from repeated `yarn dlx` execution to installed `ops-agent` package scripts.
6
+ - Added deterministic Check preflight to skip external checker calls when manifest/context quality gates already block Human Gate.
7
+ - Changed Check context policy to standard-first with strict escalation only when needed.
8
+ - Added Check timing telemetry in `task-manifest.json`.
9
+
3
10
  ## 0.1.0
4
11
 
5
12
  - Created the first local shared Ops Framework package boundary.
package/README.md CHANGED
@@ -49,16 +49,19 @@ The recommended setup is:
49
49
  corepack yarn dlx @besales/ops-framework@latest init --project-name ExampleProject --install-scripts
50
50
  ```
51
51
 
52
- This creates the project-owned `ops/**` structure, including `ops/project.ops.yaml`, task roots, cache, memory files and project-local agent config. With `--install-scripts`, it also writes package-manager scripts that call the pinned framework package through `corepack yarn dlx`; it does not add the framework as a dependency, so production installs do not need access to a local framework checkout.
52
+ This creates the project-owned `ops/**` structure, including `ops/project.ops.yaml`, task roots, cache, memory files and project-local agent config. With `--install-scripts`, it also writes package-manager scripts that call the installed `ops-agent` bin and pins the framework package in `devDependencies`.
53
53
 
54
54
  The generated scripts look like:
55
55
 
56
56
  ```json
57
57
  {
58
58
  "scripts": {
59
- "ops": "corepack yarn dlx @besales/ops-framework@0.1.0",
60
- "agent:run-check": "corepack yarn dlx @besales/ops-framework@0.1.0 run-check",
61
- "agent:run-verify": "corepack yarn dlx @besales/ops-framework@0.1.0 run-verify"
59
+ "ops": "ops-agent",
60
+ "agent:run-check": "ops-agent run-check",
61
+ "agent:run-verify": "ops-agent run-verify"
62
+ },
63
+ "devDependencies": {
64
+ "@besales/ops-framework": "0.1.0"
62
65
  }
63
66
  }
64
67
  ```
@@ -119,13 +122,13 @@ The framework blocks obvious inefficiency early, but speculative or broad optimi
119
122
  From a project root, run commands through the package entry point:
120
123
 
121
124
  ```bash
122
- corepack yarn dlx @besales/ops-framework@0.1.0 init --project-name ExampleProject --install-scripts
123
- corepack yarn dlx @besales/ops-framework@0.1.0 new-task TASK-001-example --title "Example task"
124
- corepack yarn dlx @besales/ops-framework@0.1.0 manifest TASK-001-example
125
- corepack yarn dlx @besales/ops-framework@0.1.0 preflight TASK-001-example --target execute
126
- corepack yarn dlx @besales/ops-framework@0.1.0 build-check-context TASK-001-example
127
- corepack yarn dlx @besales/ops-framework@0.1.0 run-check TASK-001-example
128
- corepack yarn dlx @besales/ops-framework@0.1.0 run-verify TASK-001-example
125
+ ops-agent init --project-name ExampleProject --install-scripts
126
+ ops-agent new-task TASK-001-example --title "Example task"
127
+ ops-agent manifest TASK-001-example
128
+ ops-agent preflight TASK-001-example --target execute
129
+ ops-agent build-check-context TASK-001-example
130
+ ops-agent run-check TASK-001-example
131
+ ops-agent run-verify TASK-001-example
129
132
  ```
130
133
 
131
134
  When installed as a package, the intended command is:
@@ -93,9 +93,9 @@ export function buildFrameworkPackageSpec({ frameworkPackage = null, frameworkVe
93
93
  }
94
94
 
95
95
  export function buildOpsScripts(packageSpec) {
96
- const run = (command) => `corepack yarn dlx ${packageSpec} ${command}`;
96
+ const run = (command) => `ops-agent ${command}`;
97
97
  return {
98
- ops: `corepack yarn dlx ${packageSpec}`,
98
+ ops: 'ops-agent',
99
99
  'agent:build-check-context': run('build-check-context'),
100
100
  'agent:validate-check-artifacts': run('validate-check-artifacts'),
101
101
  'agent:manifest': run('manifest'),
@@ -139,10 +139,30 @@ function upsertPackageScripts({ packageJsonPath, packageSpec, changes }) {
139
139
  ...(packageJson.scripts || {}),
140
140
  ...buildOpsScripts(packageSpec),
141
141
  };
142
+ const dependency = buildFrameworkPackageDependency(packageSpec);
143
+ packageJson.devDependencies = {
144
+ ...(packageJson.devDependencies || {}),
145
+ [dependency.name]: dependency.version,
146
+ };
142
147
  fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
143
148
  changes.push({ path: packageJsonPath, kind: 'file', status: 'overwritten' });
144
149
  }
145
150
 
151
+ function buildFrameworkPackageDependency(packageSpec) {
152
+ const spec = String(packageSpec || '@besales/ops-framework').trim();
153
+ const atIndex = spec.startsWith('@') ? spec.indexOf('@', 1) : spec.indexOf('@');
154
+ if (atIndex > 0) {
155
+ return {
156
+ name: spec.slice(0, atIndex),
157
+ version: spec.slice(atIndex + 1) || 'latest',
158
+ };
159
+ }
160
+ return {
161
+ name: spec,
162
+ version: 'latest',
163
+ };
164
+ }
165
+
146
166
  export function createTask({
147
167
  projectRoot = process.cwd(),
148
168
  taskId,
@@ -56,7 +56,7 @@ describe('bootstrap utilities', () => {
56
56
  expect(() => assertPortableGeneratedConfig({ projectConfigContent, agentsConfigContent })).not.toThrow();
57
57
  });
58
58
 
59
- it('can install dlx-based package scripts without adding package dependencies', () => {
59
+ it('can install local ops-agent scripts with a pinned package dependency', () => {
60
60
  const root = makeTempProject();
61
61
  fs.writeFileSync(path.join(root, 'package.json'), `${JSON.stringify({
62
62
  name: 'fixture-project',
@@ -79,10 +79,10 @@ describe('bootstrap utilities', () => {
79
79
 
80
80
  const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
81
81
  expect(packageJson.scripts.test).toBe('echo ok');
82
- expect(packageJson.scripts.ops).toBe('corepack yarn dlx @besales/ops-framework@0.1.0');
83
- expect(packageJson.scripts['agent:run-verify']).toBe('corepack yarn dlx @besales/ops-framework@0.1.0 run-verify');
82
+ expect(packageJson.scripts.ops).toBe('ops-agent');
83
+ expect(packageJson.scripts['agent:run-verify']).toBe('ops-agent run-verify');
84
84
  expect(packageJson.dependencies.leftpad).toBe('1.0.0');
85
- expect(packageJson.devDependencies?.['@besales/ops-framework']).toBeUndefined();
85
+ expect(packageJson.devDependencies?.['@besales/ops-framework']).toBe('0.1.0');
86
86
  });
87
87
 
88
88
  it('creates a minimal task artifact set', () => {
@@ -136,9 +136,9 @@ describe('buildOpsScripts', () => {
136
136
  it('generates the standard project script surface', () => {
137
137
  const scripts = buildOpsScripts('@besales/ops-framework@0.1.0');
138
138
 
139
- expect(scripts.ops).toBe('corepack yarn dlx @besales/ops-framework@0.1.0');
140
- expect(scripts['agent:quality-gates']).toBe('corepack yarn dlx @besales/ops-framework@0.1.0 quality-gates');
141
- expect(scripts['agent:test']).toBe('corepack yarn dlx @besales/ops-framework@0.1.0 test/self-test');
139
+ expect(scripts.ops).toBe('ops-agent');
140
+ expect(scripts['agent:quality-gates']).toBe('ops-agent quality-gates');
141
+ expect(scripts['agent:test']).toBe('ops-agent test/self-test');
142
142
  });
143
143
  });
144
144
 
@@ -23,13 +23,6 @@ const MEMORY_MAX_CHARS = {
23
23
  strict: Infinity,
24
24
  };
25
25
 
26
- const STRICT_TRIGGER_PATTERNS = [
27
- 'auth-security',
28
- 'prisma-schema',
29
- 'production-runtime',
30
- 'source-sync-provider',
31
- ];
32
-
33
26
  const CHECK_RELEVANT_SECTIONS = [
34
27
  'цель',
35
28
  'goal',
@@ -157,9 +150,7 @@ export function resolveLlmContextMode({ requestedMode, riskTriggers = [] } = {})
157
150
  if (requested) {
158
151
  return requested;
159
152
  }
160
- return riskTriggers.some((trigger) => STRICT_TRIGGER_PATTERNS.includes(trigger))
161
- ? 'strict'
162
- : 'standard';
153
+ return 'standard';
163
154
  }
164
155
 
165
156
  export function normalizeLlmContextMode(value) {
@@ -202,9 +202,9 @@ describe('llm input pack utilities', () => {
202
202
  expect(pack.estimatedTokens).toBeGreaterThan(Math.ceil(value.length / 2.3));
203
203
  });
204
204
 
205
- it('defaults high-risk production/source-sync tasks to strict context', () => {
206
- expect(resolveLlmContextMode({ riskTriggers: ['production-runtime'] })).toBe('strict');
207
- expect(resolveLlmContextMode({ riskTriggers: ['source-sync-provider'] })).toBe('strict');
205
+ it('defaults to standard context and escalates only after context_insufficient', () => {
206
+ expect(resolveLlmContextMode({ riskTriggers: ['production-runtime'] })).toBe('standard');
207
+ expect(resolveLlmContextMode({ riskTriggers: ['source-sync-provider'] })).toBe('standard');
208
208
  expect(resolveLlmContextMode({ riskTriggers: ['panel-ui'] })).toBe('standard');
209
209
  expect(resolveLlmContextMode({ requestedMode: 'fast', riskTriggers: ['production-runtime'] })).toBe('fast');
210
210
  });
@@ -234,7 +234,15 @@ export function writeTaskManifest(taskDir, manifest) {
234
234
  fs.writeFileSync(path.join(taskDir, TASK_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
235
235
  }
236
236
 
237
- export function recordLlmInputUsage({ taskDir, stage, packMeta, attempts = [], rerunCount = 0, now = new Date().toISOString() }) {
237
+ export function recordLlmInputUsage({
238
+ taskDir,
239
+ stage,
240
+ packMeta,
241
+ attempts = [],
242
+ rerunCount = 0,
243
+ now = new Date().toISOString(),
244
+ timing = null,
245
+ }) {
238
246
  const existing = readExistingManifest(taskDir);
239
247
  if (!existing) {
240
248
  return null;
@@ -263,6 +271,7 @@ export function recordLlmInputUsage({ taskDir, stage, packMeta, attempts = [], r
263
271
  rerunCount,
264
272
  attempts: normalizedAttempts,
265
273
  cumulativeEstimatedTokens: normalizedAttempts.reduce((sum, attempt) => sum + (attempt.estimatedTokens || 0), 0),
274
+ timing: timing || undefined,
266
275
  updatedAt: now,
267
276
  },
268
277
  },
package/bin/run-check.mjs CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  resolveConfigValue,
25
25
  resolveTaskDir,
26
26
  sha256Json,
27
+ updateStatus,
27
28
  writeTaskFile,
28
29
  } from './lib/check-context-utils.mjs';
29
30
  import {
@@ -38,7 +39,12 @@ import {
38
39
  resolveLlmContextMode,
39
40
  summarizePackForConsole,
40
41
  } from './lib/llm-input-pack-utils.mjs';
41
- import { recordLlmInputUsage } from './lib/task-manifest-utils.mjs';
42
+ import {
43
+ buildTaskManifest,
44
+ recordLlmInputUsage,
45
+ validateManifest,
46
+ writeTaskManifest,
47
+ } from './lib/task-manifest-utils.mjs';
42
48
 
43
49
  function main() {
44
50
  runMain().catch((error) => {
@@ -59,8 +65,25 @@ async function runMain() {
59
65
  const dryRun = getFlag(args, 'dry-run', false) === true;
60
66
  const noCache = getFlag(args, 'no-cache', false) === true;
61
67
  const checkerConfig = resolveCheckerConfig(args);
68
+ const runStartedAt = new Date();
62
69
 
63
- const checkContext = ensureFreshCheckContext(taskDir, taskId);
70
+ let checkContext = ensureFreshCheckContext(taskDir, taskId);
71
+ const deterministicPrecheck = syncManifestAndStatusBeforeCheck({ taskDir, taskId, checkContext });
72
+ checkContext = deterministicPrecheck.checkContext;
73
+ if (!deterministicPrecheck.ok) {
74
+ writeDeterministicPrecheckReturn({
75
+ taskDir,
76
+ taskId,
77
+ checkContext,
78
+ checkerConfig,
79
+ issues: deterministicPrecheck.issues,
80
+ startedAt: runStartedAt,
81
+ });
82
+ runValidator(taskArg);
83
+ console.log(`Checker preflight blocked ${taskId}: return_to_plan`);
84
+ console.log(`- deterministicIssues: ${deterministicPrecheck.issues.length}`);
85
+ return;
86
+ }
64
87
  const initialContextMode = resolveLlmContextMode({
65
88
  requestedMode: getFlag(args, 'context-mode') || getFlag(args, 'llm-context-mode'),
66
89
  riskTriggers: checkContext.riskTriggers,
@@ -152,6 +175,7 @@ async function runMain() {
152
175
  packMeta: promptPayload.pack.meta,
153
176
  attempts: llmInputAttempts,
154
177
  rerunCount,
178
+ timing: buildTiming(runStartedAt),
155
179
  });
156
180
  runValidator(taskArg);
157
181
  console.log(`Checker blocked for ${taskId}: strict context pack over cap`);
@@ -166,6 +190,7 @@ async function runMain() {
166
190
  packMeta: promptPayload.pack.meta,
167
191
  attempts: llmInputAttempts,
168
192
  rerunCount,
193
+ timing: buildTiming(runStartedAt),
169
194
  });
170
195
  console.log(`Checker cache hit for ${taskId}: ${cacheKeySha}`);
171
196
  return;
@@ -197,6 +222,7 @@ async function runMain() {
197
222
  packMeta: promptPayload.pack.meta,
198
223
  attempts: llmInputAttempts,
199
224
  rerunCount,
225
+ timing: buildTiming(runStartedAt),
200
226
  });
201
227
  runValidator(taskArg);
202
228
  throw new Error(`Checker provider failed with ${failureReason}: ${error.message}`);
@@ -229,6 +255,7 @@ async function runMain() {
229
255
  packMeta: promptPayload.pack.meta,
230
256
  attempts: llmInputAttempts,
231
257
  rerunCount,
258
+ timing: buildTiming(runStartedAt),
232
259
  });
233
260
  runValidator(taskArg);
234
261
  console.log(`Checker run completed for ${taskId}: ${providerOutput.checkResultJson?.verdict}`);
@@ -247,6 +274,142 @@ function buildAttemptRecord(packMeta, outcome) {
247
274
  };
248
275
  }
249
276
 
277
+ function buildTiming(startedAt, completedAt = new Date()) {
278
+ return {
279
+ startedAt: startedAt.toISOString(),
280
+ completedAt: completedAt.toISOString(),
281
+ durationMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
282
+ };
283
+ }
284
+
285
+ function syncManifestAndStatusBeforeCheck({ taskDir, taskId, checkContext }) {
286
+ updateStatus(taskDir, {
287
+ planSha: `\`${checkContext.planSha}\``,
288
+ memorySha: `\`${checkContext.memorySha}\``,
289
+ checkResult: '- `check.result.json`: pending_current_check',
290
+ supervisorAction: 'Synchronized manifest/status before Check.',
291
+ nextStep: 'Run Check or fix deterministic plan quality gates before Human Gate.',
292
+ });
293
+
294
+ const syncedCheckContext = ensureFreshCheckContext(taskDir, taskId);
295
+ const manifest = buildTaskManifest({ taskDir });
296
+ writeTaskManifest(taskDir, manifest);
297
+
298
+ const issues = validateManifest(manifest).map((message) => ({
299
+ category: 'manifest_validation',
300
+ message,
301
+ }));
302
+ if (!manifest.context.checkContextCurrent) {
303
+ issues.push({
304
+ category: 'stale_check_context',
305
+ message: 'check-context.json/checker-context-pack.md is stale after synchronization.',
306
+ });
307
+ }
308
+ for (const signal of manifest.qualitySignals || []) {
309
+ issues.push({
310
+ category: 'plan_quality_gate',
311
+ message: signal,
312
+ });
313
+ }
314
+
315
+ return {
316
+ ok: issues.length === 0,
317
+ manifest,
318
+ issues,
319
+ checkContext: syncedCheckContext,
320
+ };
321
+ }
322
+
323
+ function writeDeterministicPrecheckReturn({
324
+ taskDir,
325
+ taskId,
326
+ checkContext,
327
+ checkerConfig,
328
+ issues,
329
+ startedAt,
330
+ }) {
331
+ const findings = issues.map((issue, index) => ({
332
+ id: `F-${String(index + 1).padStart(3, '0')}`,
333
+ severity: 'blocking',
334
+ claimCategory: issue.category === 'plan_quality_gate' ? 'risk_unaddressed' : 'missing_evidence',
335
+ claim: issue.message,
336
+ evidenceRefs: [
337
+ {
338
+ type: 'file',
339
+ ref: issue.category === 'plan_quality_gate' ? 'plan.md' : 'task-manifest.json',
340
+ },
341
+ ],
342
+ affectedPlanSections: ['Plan/Check'],
343
+ expectedCorrection: expectedCorrectionForPrecheckIssue(issue),
344
+ }));
345
+ const result = {
346
+ taskId,
347
+ stage: 'Check',
348
+ checkerProvider: 'deterministic-precheck',
349
+ checkerModel: 'none',
350
+ planSha: checkContext.planSha,
351
+ memorySha: checkContext.memorySha,
352
+ riskProfile: checkContext.riskProfile,
353
+ verdict: 'return_to_plan',
354
+ failureReason: null,
355
+ blockingFindings: findings.length,
356
+ nonBlockingFindings: 0,
357
+ humanQuestions: 0,
358
+ findings,
359
+ readyForHumanGate: false,
360
+ createdAt: new Date().toISOString(),
361
+ };
362
+ const markdown = [
363
+ '# Check',
364
+ '',
365
+ '## итоговая оценка',
366
+ '',
367
+ '`return_to_plan` from deterministic pre-check.',
368
+ '',
369
+ 'External checker was not invoked because machine-readable plan/context gates already found blocking issues.',
370
+ '',
371
+ '## structured findings',
372
+ '',
373
+ '| ID | Severity | Category | Claim | Expected correction |',
374
+ '| --- | --- | --- | --- | --- |',
375
+ ...findings.map((finding) => `| ${finding.id} | ${finding.severity} | ${finding.claimCategory} | ${escapeTableCell(finding.claim)} | ${escapeTableCell(finding.expectedCorrection)} |`),
376
+ '',
377
+ '## рекомендация supervisor',
378
+ '',
379
+ '`return_to_plan`',
380
+ '',
381
+ '## Timing',
382
+ '',
383
+ `- Duration: ${buildTiming(startedAt).durationMs}ms`,
384
+ ].join('\n');
385
+
386
+ writeTaskFile(taskDir, 'check.md', markdown);
387
+ writeTaskFile(taskDir, 'check.result.json', JSON.stringify(result, null, 2));
388
+ updateStatus(taskDir, {
389
+ checkVerdict: '`return_to_plan`',
390
+ checkResult: '- `check.result.json`: current',
391
+ supervisorAction: 'Deterministic Check preflight blocked external checker invocation.',
392
+ nextStep: 'Fix deterministic Check findings, then rerun Check.',
393
+ humanApproval: 'no',
394
+ });
395
+ ensureFreshCheckContext(taskDir, taskId);
396
+ appendOrchestrationLog(taskDir, `deterministic Check preflight returned return_to_plan; findings=${findings.length}; external checker skipped`);
397
+ }
398
+
399
+ function expectedCorrectionForPrecheckIssue(issue) {
400
+ if (issue.category === 'plan_quality_gate') {
401
+ return 'Update plan.md so deterministic quality gates are complete before external Check.';
402
+ }
403
+ if (issue.category === 'stale_check_context') {
404
+ return 'Regenerate check context and task manifest before rerunning Check.';
405
+ }
406
+ return 'Fix task-manifest/check-context consistency before external Check.';
407
+ }
408
+
409
+ function escapeTableCell(value) {
410
+ return String(value || '').replace(/\|/g, '\\|').replace(/\n/g, ' ').trim();
411
+ }
412
+
250
413
  function buildCheckerCacheKey({
251
414
  taskId,
252
415
  checkContext,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@besales/ops-framework",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "ops-agent": "bin/ops-agent.mjs"