hive-lite 0.1.9 → 0.2.0

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/src/cli.js CHANGED
@@ -1,10 +1,13 @@
1
+ const fs = require('fs');
1
2
  const path = require('path');
2
3
  const { version: packageVersion } = require('../package.json');
3
4
  const { requireGitRepo } = require('./lib/git');
4
5
  const { evaluateMapHealth } = require('./lib/health');
6
+ const { readIntentTarget, saveStartIntent, validateIntentTarget } = require('./lib/intent');
5
7
  const { createMapPrompt, initProject, loadProjectMap, suggestMap, verifyProjectMap } = require('./lib/map');
6
8
  const { createContextPacket } = require('./lib/context');
7
9
  const { evaluateNextAction } = require('./lib/next');
10
+ const { runOperator } = require('./lib/operator');
8
11
  const { installSkills, skillsDoctor, syncSkills } = require('./lib/skills');
9
12
  const { patternDisplay } = require('./lib/scope');
10
13
  const { evaluateWorkspaceStatus } = require('./lib/status');
@@ -73,6 +76,12 @@ Usage:
73
76
  hive-lite skills doctor [--agent codex|claude|gemini|all] [--path <skills-dir>] [--json]
74
77
  hive-lite skills install --agent codex|claude|gemini|all [--force] [--dry-run] [--json]
75
78
  hive-lite skills sync --agent codex|claude|gemini|all [--dry-run] [--json]
79
+ hive-lite intent save --from <file> --json
80
+ hive-lite intent save --stdin --json
81
+ hive-lite intent validate <intent_id_or_path> --json
82
+ hive-lite intent show <intent_id_or_path> --json
83
+ hive-lite operator run "<clear requirement>" [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file]
84
+ hive-lite operator run --intent <intent_id_or_path> [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file]
76
85
  hive-lite find "<intent>" [--area <id>] [--max-files 8] [--explain] [--json]
77
86
  hive-lite check [changeId] [--context ctx_id] [--json]
78
87
  hive-lite validate [changeId] [--cmd "..."] [--profile <id>]
@@ -223,10 +232,12 @@ function printContext(result) {
223
232
  console.log('');
224
233
  console.log('Candidate Phase Seeds:');
225
234
  for (const item of packet.candidatePhaseSeeds) {
235
+ const fromSplit = packet.splitNote && packet.splitNote.id ? ` --from-split ${packet.splitNote.id}` : '';
226
236
  console.log(` - ${item.id} (${item.areaId})`);
227
- console.log(` find: ${item.findIntent}`);
228
- if (item.evidenceClasses && item.evidenceClasses.length > 0) console.log(` evidence: ${item.evidenceClasses.join(', ')}`);
229
- if (item.validationProfiles && item.validationProfiles.length > 0) console.log(` validation: ${item.validationProfiles.join(', ')}`);
237
+ console.log(' copyable find command:');
238
+ console.log(` hive-lite find ${JSON.stringify(item.findIntent)}${fromSplit} --phase ${item.id} --area ${item.areaId}`);
239
+ if (item.evidenceClasses && item.evidenceClasses.length > 0) console.log(` expected evidence: ${item.evidenceClasses.join(', ')}`);
240
+ if (item.validationProfiles && item.validationProfiles.length > 0) console.log(` expected validation: ${item.validationProfiles.join(', ')}`);
230
241
  console.log(` risk: ${item.risk}`);
231
242
  }
232
243
  }
@@ -240,6 +251,15 @@ function printContext(result) {
240
251
  console.log(' blocking:');
241
252
  for (const item of packet.writePlan.blockingWarnings) console.log(` - ${item.code}: ${item.message}`);
242
253
  }
254
+ if (packet.writePlan.selectedWritableCertificates && packet.writePlan.selectedWritableCertificates.length > 0) {
255
+ console.log(' certified direct:');
256
+ for (const item of packet.writePlan.selectedWritableCertificates) {
257
+ const artifact = item.artifact ? ` ${item.artifact}` : '';
258
+ const target = item.targetIdentity ? ` for ${item.targetIdentity}` : '';
259
+ console.log(` - ${item.path}: ${item.operation}${artifact}${target}`);
260
+ if (item.certifiedBy && item.certifiedBy.mapEntry) console.log(` certified by: ${item.certifiedBy.mapEntry}`);
261
+ }
262
+ }
243
263
  }
244
264
  if (packet.writePlan && packet.writePlan.referenceFiles && packet.writePlan.referenceFiles.length > 0) {
245
265
  console.log('');
@@ -396,6 +416,18 @@ function printCheck(root, result) {
396
416
  console.log(' selected direct:');
397
417
  for (const item of change.writePlanStatus.selectedWritableDirect) console.log(` - ${item}`);
398
418
  }
419
+ if (change.writePlanStatus.selectedWritableCertificates && change.writePlanStatus.selectedWritableCertificates.length > 0) {
420
+ console.log(' certificates:');
421
+ for (const item of change.writePlanStatus.selectedWritableCertificates) {
422
+ const artifact = item.artifact ? ` ${item.artifact}` : '';
423
+ const target = item.targetIdentity ? ` for ${item.targetIdentity}` : '';
424
+ console.log(` - ${item.path}: ${item.operation}${artifact}${target}`);
425
+ }
426
+ }
427
+ if (change.writePlanStatus.uncertifiedWrites && change.writePlanStatus.uncertifiedWrites.length > 0) {
428
+ console.log(' uncertified writes:');
429
+ for (const item of change.writePlanStatus.uncertifiedWrites) console.log(` - ${item.path}: ${item.operation}`);
430
+ }
399
431
  if (change.writePlanStatus.referenceOnlyTouched && change.writePlanStatus.referenceOnlyTouched.length > 0) {
400
432
  console.log(' reference-only touched:');
401
433
  for (const item of change.writePlanStatus.referenceOnlyTouched) console.log(` - ${item.path}: ${item.reason || item.reference}`);
@@ -792,6 +824,56 @@ function printSkillsMutation(result) {
792
824
  }
793
825
  }
794
826
 
827
+ function printIntentErrors(result) {
828
+ if (result.errors && result.errors.length > 0) {
829
+ console.log('');
830
+ console.log('Errors:');
831
+ for (const error of result.errors) console.log(` - ${error.code}: ${error.message}`);
832
+ }
833
+ if (result.warnings && result.warnings.length > 0) {
834
+ console.log('');
835
+ console.log('Warnings:');
836
+ for (const warning of result.warnings) console.log(` - ${warning.code}: ${warning.message}`);
837
+ }
838
+ }
839
+
840
+ function printIntentSave(result) {
841
+ if (!result.saved) {
842
+ console.log('Start Intent Handoff not saved.');
843
+ printIntentErrors(result);
844
+ return;
845
+ }
846
+ console.log('Start Intent Handoff saved.');
847
+ console.log('');
848
+ console.log(`Intent: ${result.intent_id}`);
849
+ console.log(`Path: ${result.paths.markdown}`);
850
+ console.log(`JSON: ${result.paths.json}`);
851
+ if (result.warnings && result.warnings.length > 0) printIntentErrors({ errors: [], warnings: result.warnings });
852
+ console.log('');
853
+ console.log('Next (run from this repo root in your terminal):');
854
+ console.log(` hive-lite operator run --intent ${result.intent_id}`);
855
+ console.log('');
856
+ console.log('Equivalent path form:');
857
+ console.log(` hive-lite operator run --intent ${result.paths.markdown}`);
858
+ }
859
+
860
+ function printIntentValidate(result) {
861
+ console.log(`Start Intent Handoff: ${result.intent_id || '(unknown)'}`);
862
+ console.log(`Status: ${result.valid ? 'valid' : 'invalid'}`);
863
+ console.log(`Path: ${result.paths.markdown}`);
864
+ printIntentErrors(result);
865
+ }
866
+
867
+ function printIntentShow(result) {
868
+ console.log(`Start Intent Handoff: ${result.intent_id || '(unknown)'}`);
869
+ console.log(`Status: ${result.status || '(unknown)'}`);
870
+ console.log(`Path: ${result.paths.markdown}`);
871
+ console.log('');
872
+ console.log('Find Intent:');
873
+ console.log(result.findIntent || '(none)');
874
+ printIntentErrors(result.validation || {});
875
+ }
876
+
795
877
  async function main(argv) {
796
878
  const [command, subcommand, subsubcommand, ...rest] = argv;
797
879
  if (!command || isHelpArg(command)) {
@@ -862,6 +944,67 @@ async function main(argv) {
862
944
  throw new Error('skills requires a subcommand: doctor, install, or sync');
863
945
  }
864
946
 
947
+ if (command === 'intent') {
948
+ const root = requireGitRepo(process.cwd(), 'hive-lite intent');
949
+ const parsed = parseArgs([subsubcommand, ...rest].filter((item) => item !== undefined));
950
+
951
+ if (subcommand === 'save') {
952
+ if (parsed.flags.from && parsed.flags.stdin) throw new Error('intent save accepts either --from <file> or --stdin, not both');
953
+ if (!parsed.flags.from && !parsed.flags.stdin) throw new Error('intent save requires --from <file> or --stdin');
954
+ const source = parsed.flags.stdin ? 'stdin' : parsed.flags.from;
955
+ const markdown = parsed.flags.stdin
956
+ ? fs.readFileSync(0, 'utf8')
957
+ : fs.readFileSync(path.resolve(process.cwd(), parsed.flags.from), 'utf8');
958
+ const result = saveStartIntent(root, markdown, { source });
959
+ if (parsed.flags.json) console.log(JSON.stringify(result, null, 2));
960
+ else printIntentSave(result);
961
+ if (!result.saved) process.exitCode = 1;
962
+ return;
963
+ }
964
+
965
+ if (subcommand === 'validate') {
966
+ const target = parsed.positional[0];
967
+ if (!target) throw new Error('intent validate requires an intent id or path');
968
+ const result = validateIntentTarget(root, process.cwd(), target);
969
+ if (parsed.flags.json) console.log(JSON.stringify(result, null, 2));
970
+ else printIntentValidate(result);
971
+ if (!result.valid) process.exitCode = 1;
972
+ return;
973
+ }
974
+
975
+ if (subcommand === 'show') {
976
+ const target = parsed.positional[0];
977
+ if (!target) throw new Error('intent show requires an intent id or path');
978
+ const result = readIntentTarget(root, process.cwd(), target);
979
+ if (parsed.flags.json) console.log(JSON.stringify(result, null, 2));
980
+ else printIntentShow(result);
981
+ return;
982
+ }
983
+
984
+ throw new Error('intent requires a subcommand: save, validate, or show');
985
+ }
986
+
987
+ if (command === 'operator') {
988
+ if (subcommand !== 'run') throw new Error('operator requires a subcommand: run');
989
+ const root = requireGitRepo(process.cwd(), 'hive-lite operator run');
990
+ const parsed = parseArgs([subsubcommand, ...rest].filter((item) => item !== undefined));
991
+ if (parsed.flags.intent && parsed.positional.length > 0) {
992
+ throw new Error('operator run accepts either a clear requirement or --intent, not both');
993
+ }
994
+ await runOperator(process.cwd(), {
995
+ root,
996
+ requirement: parsed.positional.join(' ').trim(),
997
+ intent: parsed.flags.intent || null,
998
+ operatorAgentCommand: parsed.flags.operatorAgentCommand || parsed.flags.agentCommand,
999
+ operatorAgentMode: parsed.flags.operatorAgentMode || parsed.flags.agentMode,
1000
+ }, {
1001
+ stdin: process.stdin,
1002
+ stdout: process.stdout,
1003
+ stderr: process.stderr,
1004
+ });
1005
+ return;
1006
+ }
1007
+
865
1008
  if (command === 'find') {
866
1009
  const parsed = parseArgs([subcommand, subsubcommand, ...rest].filter((item) => item !== undefined));
867
1010
  const intent = parsed.positional.join(' ').trim();
package/src/lib/change.js CHANGED
@@ -16,6 +16,12 @@ function sha256(value) {
16
16
  return createHash('sha256').update(value || '').digest('hex');
17
17
  }
18
18
 
19
+ function looksLikeFilePattern(pattern) {
20
+ if (String(pattern || '').includes('*')) return false;
21
+ const base = path.basename(String(pattern || ''));
22
+ return /\.[A-Za-z0-9]+$/.test(base);
23
+ }
24
+
19
25
  function changeDir(root, id) {
20
26
  return path.join(hiveDir(root), 'changes', id);
21
27
  }
@@ -314,6 +320,61 @@ function validationPlanFromContextOrMap(context, map) {
314
320
  }] : [];
315
321
  }
316
322
 
323
+ function createPatternsFromContext(context) {
324
+ const scope = context && context.scope ? context.scope : {};
325
+ return normalizePatternList(scope.writableCreatePatterns || [], {
326
+ source: 'writable_create_patterns',
327
+ operations: ['create_file'],
328
+ }).map(itemPattern).filter(Boolean);
329
+ }
330
+
331
+ function selectedWritableCertificatesFromContext(context) {
332
+ const writePlan = context && context.writePlan ? context.writePlan : null;
333
+ return writePlan && Array.isArray(writePlan.selectedWritableCertificates)
334
+ ? writePlan.selectedWritableCertificates
335
+ : [];
336
+ }
337
+
338
+ function certificateOperation(certificate) {
339
+ return certificate.operation || certificate.operationKind || null;
340
+ }
341
+
342
+ function certificatePath(certificate) {
343
+ return certificate.path || certificate.scope || certificate.pattern || null;
344
+ }
345
+
346
+ function certificateCoversFile(certificate, file, operation) {
347
+ const pattern = certificatePath(certificate);
348
+ if (!pattern || !matchesPattern(file, pattern)) return false;
349
+ const certifiedOperation = certificateOperation(certificate);
350
+ if (!certifiedOperation) return true;
351
+ return certifiedOperation === operation;
352
+ }
353
+
354
+ function matchesReviewGatedScope(file, context) {
355
+ const scope = context && context.scope ? context.scope : {};
356
+ const reviewGated = [
357
+ ...normalizePatternList(scope.writableConditional || [], { requiresReview: true }),
358
+ ...normalizePatternList(scope.writableBroadFallback || [], { requiresReview: true }),
359
+ ];
360
+ return reviewGated.some((item) => matchesPattern(file, itemPattern(item)));
361
+ }
362
+
363
+ function selectedHasCreatePermission(file, context, selectedWritableDirect, selectedWritableCertificates = []) {
364
+ if (selectedWritableCertificates.length > 0) {
365
+ return selectedWritableCertificates.some((certificate) => certificateCoversFile(certificate, file, 'create_file'));
366
+ }
367
+ const selected = selectedWritableDirect || [];
368
+ const selectedSet = new Set(selected);
369
+ const createPatterns = createPatternsFromContext(context);
370
+ if (createPatterns.some((pattern) => selectedSet.has(pattern) && matchesPattern(file, pattern))) return true;
371
+
372
+ return selected.some((pattern) => (
373
+ !looksLikeFilePattern(pattern)
374
+ && matchesPattern(file, pattern)
375
+ ));
376
+ }
377
+
317
378
  function evaluateWritePlanStatus(files, context) {
318
379
  if (!context) {
319
380
  return {
@@ -321,7 +382,10 @@ function evaluateWritePlanStatus(files, context) {
321
382
  contextMode: null,
322
383
  blockingReasons: [],
323
384
  selectedWritableDirect: [],
385
+ selectedWritableCertificates: [],
324
386
  referenceOnlyTouched: [],
387
+ unknownNewFiles: [],
388
+ uncertifiedWrites: [],
325
389
  requiredWrites: [],
326
390
  blockingWarnings: [],
327
391
  };
@@ -332,9 +396,12 @@ function evaluateWritePlanStatus(files, context) {
332
396
  ? writePlan.blockingWarnings
333
397
  : [];
334
398
  const selectedWritableDirect = Array.isArray(context.writableScope) ? context.writableScope : [];
399
+ const selectedWritableCertificates = selectedWritableCertificatesFromContext(context);
335
400
  const changedPaths = files.map((item) => item.path || item);
336
401
  const referenceFiles = writePlan && Array.isArray(writePlan.referenceFiles) ? writePlan.referenceFiles : [];
337
402
  const referenceOnlyTouched = [];
403
+ const unknownNewFiles = [];
404
+ const uncertifiedWrites = [];
338
405
  for (const file of changedPaths) {
339
406
  const reference = referenceFiles.find((item) => matchesPattern(file, item.path || item.pattern));
340
407
  if (reference) {
@@ -345,6 +412,23 @@ function evaluateWritePlanStatus(files, context) {
345
412
  });
346
413
  }
347
414
  }
415
+ for (const file of files) {
416
+ const filePath = file.path || file;
417
+ if (file.status !== 'untracked') continue;
418
+ if (!selectedHasCreatePermission(filePath, context, selectedWritableDirect, selectedWritableCertificates)) unknownNewFiles.push(filePath);
419
+ }
420
+ if (selectedWritableCertificates.length > 0) {
421
+ for (const file of files) {
422
+ const filePath = file.path || file;
423
+ const operation = file.status === 'untracked' ? 'create_file' : 'update_existing';
424
+ if (
425
+ !(operation === 'update_existing' && matchesReviewGatedScope(filePath, context))
426
+ && !selectedWritableCertificates.some((certificate) => certificateCoversFile(certificate, filePath, operation))
427
+ ) {
428
+ uncertifiedWrites.push({ path: filePath, operation });
429
+ }
430
+ }
431
+ }
348
432
 
349
433
  const blockingReasons = [];
350
434
  if (context.mode && context.mode !== 'edit_context') {
@@ -356,6 +440,12 @@ function evaluateWritePlanStatus(files, context) {
356
440
  for (const item of referenceOnlyTouched) {
357
441
  blockingReasons.push(`${item.path} touched reference-only file ${item.reference}`);
358
442
  }
443
+ for (const file of unknownNewFiles) {
444
+ blockingReasons.push(`UNKNOWN_NEW_FILE: ${file} is not covered by selected create-capable Direct Writable scope`);
445
+ }
446
+ for (const item of uncertifiedWrites) {
447
+ blockingReasons.push(`UNCERTIFIED_WRITE: ${item.path} ${item.operation} is not covered by a selected writable certificate`);
448
+ }
359
449
  if (selectedWritableDirect.length === 0 && changedPaths.length > 0) {
360
450
  blockingReasons.push('context has no selected Direct Writable scope');
361
451
  }
@@ -365,7 +455,10 @@ function evaluateWritePlanStatus(files, context) {
365
455
  contextMode: context.mode || null,
366
456
  blockingReasons: [...new Set(blockingReasons)],
367
457
  selectedWritableDirect,
458
+ selectedWritableCertificates,
368
459
  referenceOnlyTouched,
460
+ unknownNewFiles,
461
+ uncertifiedWrites,
369
462
  requiredWrites: writePlan && Array.isArray(writePlan.hypotheses) ? writePlan.hypotheses : [],
370
463
  blockingWarnings,
371
464
  };