@nurix/nustack 0.23.14 → 0.23.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.
@@ -2682,6 +2682,436 @@
2682
2682
  ],
2683
2683
  "additionalProperties": false
2684
2684
  },
2685
+ "SessionBrief": {
2686
+ "type": "object",
2687
+ "properties": {
2688
+ "previousHandover": {
2689
+ "anyOf": [
2690
+ {
2691
+ "type": "object",
2692
+ "properties": {
2693
+ "sessionId": {
2694
+ "type": "string",
2695
+ "minLength": 1,
2696
+ "maxLength": 128
2697
+ },
2698
+ "runtime": {
2699
+ "anyOf": [
2700
+ {
2701
+ "type": "string",
2702
+ "maxLength": 64
2703
+ },
2704
+ {
2705
+ "type": "null"
2706
+ }
2707
+ ]
2708
+ },
2709
+ "handover": {
2710
+ "type": "string",
2711
+ "maxLength": 20000
2712
+ },
2713
+ "createdAt": {
2714
+ "type": "string",
2715
+ "maxLength": 64
2716
+ }
2717
+ },
2718
+ "required": [
2719
+ "sessionId",
2720
+ "runtime",
2721
+ "handover",
2722
+ "createdAt"
2723
+ ],
2724
+ "additionalProperties": {}
2725
+ },
2726
+ {
2727
+ "type": "null"
2728
+ }
2729
+ ]
2730
+ },
2731
+ "openObligations": {
2732
+ "type": "object",
2733
+ "properties": {
2734
+ "total": {
2735
+ "type": "integer",
2736
+ "minimum": 0,
2737
+ "maximum": 9007199254740991
2738
+ },
2739
+ "items": {
2740
+ "maxItems": 50,
2741
+ "type": "array",
2742
+ "items": {
2743
+ "type": "object",
2744
+ "properties": {
2745
+ "naturalKey": {
2746
+ "type": "string",
2747
+ "minLength": 1,
2748
+ "maxLength": 1024
2749
+ },
2750
+ "planAnchor": {
2751
+ "anyOf": [
2752
+ {
2753
+ "type": "string",
2754
+ "maxLength": 256
2755
+ },
2756
+ {
2757
+ "type": "null"
2758
+ }
2759
+ ]
2760
+ },
2761
+ "sessionId": {
2762
+ "anyOf": [
2763
+ {
2764
+ "type": "string",
2765
+ "maxLength": 128
2766
+ },
2767
+ {
2768
+ "type": "null"
2769
+ }
2770
+ ]
2771
+ },
2772
+ "payload": {
2773
+ "type": "object",
2774
+ "properties": {},
2775
+ "additionalProperties": {}
2776
+ },
2777
+ "createdAt": {
2778
+ "type": "string",
2779
+ "maxLength": 64
2780
+ }
2781
+ },
2782
+ "required": [
2783
+ "naturalKey",
2784
+ "planAnchor",
2785
+ "sessionId",
2786
+ "payload",
2787
+ "createdAt"
2788
+ ],
2789
+ "additionalProperties": {}
2790
+ }
2791
+ }
2792
+ },
2793
+ "required": [
2794
+ "total",
2795
+ "items"
2796
+ ],
2797
+ "additionalProperties": {}
2798
+ },
2799
+ "generatedAt": {
2800
+ "type": "string",
2801
+ "maxLength": 64
2802
+ }
2803
+ },
2804
+ "required": [
2805
+ "previousHandover",
2806
+ "openObligations",
2807
+ "generatedAt"
2808
+ ],
2809
+ "additionalProperties": {}
2810
+ },
2811
+ "SessionEvent": {
2812
+ "type": "object",
2813
+ "properties": {
2814
+ "sessionId": {
2815
+ "type": "string",
2816
+ "minLength": 1,
2817
+ "maxLength": 128
2818
+ },
2819
+ "runtimeSessionId": {
2820
+ "type": "string",
2821
+ "minLength": 1,
2822
+ "maxLength": 128
2823
+ },
2824
+ "phase": {
2825
+ "type": "string",
2826
+ "enum": [
2827
+ "start",
2828
+ "end"
2829
+ ]
2830
+ },
2831
+ "occurredAt": {
2832
+ "type": "string",
2833
+ "allOf": [
2834
+ {
2835
+ "type": "string",
2836
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
2837
+ },
2838
+ {
2839
+ "type": "string",
2840
+ "pattern": "T\\d{2}:\\d{2}:\\d{2}"
2841
+ }
2842
+ ]
2843
+ },
2844
+ "extension": {
2845
+ "type": "object",
2846
+ "properties": {
2847
+ "runtime": {
2848
+ "type": "string",
2849
+ "enum": [
2850
+ "claude_code",
2851
+ "codex",
2852
+ "opencode",
2853
+ "copilot_cli",
2854
+ "nustack"
2855
+ ]
2856
+ },
2857
+ "model": {
2858
+ "type": "string",
2859
+ "minLength": 1,
2860
+ "maxLength": 128
2861
+ },
2862
+ "harnessSemver": {
2863
+ "type": "string",
2864
+ "maxLength": 64,
2865
+ "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$"
2866
+ },
2867
+ "harnessDigest": {
2868
+ "type": "string",
2869
+ "pattern": "^[0-9a-f]{64}$"
2870
+ },
2871
+ "configDigest": {
2872
+ "type": "string",
2873
+ "pattern": "^[0-9a-f]{64}$"
2874
+ },
2875
+ "handover": {
2876
+ "type": "string",
2877
+ "minLength": 1,
2878
+ "maxLength": 20000
2879
+ }
2880
+ },
2881
+ "required": [
2882
+ "runtime"
2883
+ ],
2884
+ "additionalProperties": false
2885
+ },
2886
+ "isEngineSession": {
2887
+ "type": "boolean"
2888
+ },
2889
+ "startCommit": {
2890
+ "type": "string",
2891
+ "pattern": "^[0-9a-f]{7,64}$"
2892
+ }
2893
+ },
2894
+ "required": [
2895
+ "sessionId",
2896
+ "phase",
2897
+ "occurredAt",
2898
+ "extension"
2899
+ ],
2900
+ "additionalProperties": false
2901
+ },
2902
+ "SessionEventsRequest": {
2903
+ "type": "object",
2904
+ "properties": {
2905
+ "events": {
2906
+ "maxItems": 200,
2907
+ "type": "array",
2908
+ "items": {
2909
+ "type": "object",
2910
+ "properties": {
2911
+ "sessionId": {
2912
+ "type": "string",
2913
+ "minLength": 1,
2914
+ "maxLength": 128
2915
+ },
2916
+ "runtimeSessionId": {
2917
+ "type": "string",
2918
+ "minLength": 1,
2919
+ "maxLength": 128
2920
+ },
2921
+ "phase": {
2922
+ "type": "string",
2923
+ "enum": [
2924
+ "start",
2925
+ "end"
2926
+ ]
2927
+ },
2928
+ "occurredAt": {
2929
+ "type": "string",
2930
+ "allOf": [
2931
+ {
2932
+ "type": "string",
2933
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
2934
+ },
2935
+ {
2936
+ "type": "string",
2937
+ "pattern": "T\\d{2}:\\d{2}:\\d{2}"
2938
+ }
2939
+ ]
2940
+ },
2941
+ "extension": {
2942
+ "type": "object",
2943
+ "properties": {
2944
+ "runtime": {
2945
+ "type": "string",
2946
+ "enum": [
2947
+ "claude_code",
2948
+ "codex",
2949
+ "opencode",
2950
+ "copilot_cli",
2951
+ "nustack"
2952
+ ]
2953
+ },
2954
+ "model": {
2955
+ "type": "string",
2956
+ "minLength": 1,
2957
+ "maxLength": 128
2958
+ },
2959
+ "harnessSemver": {
2960
+ "type": "string",
2961
+ "maxLength": 64,
2962
+ "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$"
2963
+ },
2964
+ "harnessDigest": {
2965
+ "type": "string",
2966
+ "pattern": "^[0-9a-f]{64}$"
2967
+ },
2968
+ "configDigest": {
2969
+ "type": "string",
2970
+ "pattern": "^[0-9a-f]{64}$"
2971
+ },
2972
+ "handover": {
2973
+ "type": "string",
2974
+ "minLength": 1,
2975
+ "maxLength": 20000
2976
+ }
2977
+ },
2978
+ "required": [
2979
+ "runtime"
2980
+ ],
2981
+ "additionalProperties": false
2982
+ },
2983
+ "isEngineSession": {
2984
+ "type": "boolean"
2985
+ },
2986
+ "startCommit": {
2987
+ "type": "string",
2988
+ "pattern": "^[0-9a-f]{7,64}$"
2989
+ }
2990
+ },
2991
+ "required": [
2992
+ "sessionId",
2993
+ "phase",
2994
+ "occurredAt",
2995
+ "extension"
2996
+ ],
2997
+ "additionalProperties": false
2998
+ }
2999
+ },
3000
+ "labelRows": {
3001
+ "maxItems": 200,
3002
+ "type": "array",
3003
+ "items": {
3004
+ "type": "object",
3005
+ "properties": {
3006
+ "id": {
3007
+ "type": "string",
3008
+ "minLength": 1,
3009
+ "maxLength": 128
3010
+ },
3011
+ "sessionId": {
3012
+ "type": "string",
3013
+ "minLength": 1,
3014
+ "maxLength": 128
3015
+ },
3016
+ "agentType": {
3017
+ "type": "string",
3018
+ "minLength": 1,
3019
+ "maxLength": 128
3020
+ },
3021
+ "path": {
3022
+ "anyOf": [
3023
+ {
3024
+ "type": "string",
3025
+ "maxLength": 512
3026
+ },
3027
+ {
3028
+ "type": "null"
3029
+ }
3030
+ ]
3031
+ },
3032
+ "estimatedTokens": {
3033
+ "type": "integer",
3034
+ "minimum": 0,
3035
+ "maximum": 9007199254740991
3036
+ },
3037
+ "returnedTokens": {
3038
+ "type": "integer",
3039
+ "minimum": 0,
3040
+ "maximum": 9007199254740991
3041
+ },
3042
+ "handler": {
3043
+ "type": "string",
3044
+ "enum": [
3045
+ "outline",
3046
+ "bypass",
3047
+ "digest",
3048
+ "full_read",
3049
+ "local_model",
3050
+ "redirect",
3051
+ "pack_hit",
3052
+ "pack_miss"
3053
+ ]
3054
+ },
3055
+ "checkResult": {
3056
+ "anyOf": [
3057
+ {
3058
+ "type": "string",
3059
+ "enum": [
3060
+ "pass",
3061
+ "fail",
3062
+ "budget",
3063
+ "unavailable",
3064
+ "latched"
3065
+ ]
3066
+ },
3067
+ {
3068
+ "type": "null"
3069
+ }
3070
+ ]
3071
+ },
3072
+ "isBypassUsed": {
3073
+ "type": "boolean"
3074
+ },
3075
+ "latencyMs": {
3076
+ "type": "integer",
3077
+ "minimum": 0,
3078
+ "maximum": 9007199254740991
3079
+ },
3080
+ "tokensKeptOut": {
3081
+ "type": "integer",
3082
+ "minimum": -9007199254740991,
3083
+ "maximum": 9007199254740991
3084
+ },
3085
+ "createdAt": {
3086
+ "type": "integer",
3087
+ "minimum": 0,
3088
+ "maximum": 9007199254740991
3089
+ }
3090
+ },
3091
+ "required": [
3092
+ "id",
3093
+ "sessionId",
3094
+ "agentType",
3095
+ "path",
3096
+ "estimatedTokens",
3097
+ "returnedTokens",
3098
+ "handler",
3099
+ "checkResult",
3100
+ "isBypassUsed",
3101
+ "latencyMs",
3102
+ "tokensKeptOut",
3103
+ "createdAt"
3104
+ ],
3105
+ "additionalProperties": false
3106
+ }
3107
+ }
3108
+ },
3109
+ "required": [
3110
+ "events",
3111
+ "labelRows"
3112
+ ],
3113
+ "additionalProperties": false
3114
+ },
2685
3115
  "SessionRowExtension": {
2686
3116
  "type": "object",
2687
3117
  "properties": {
@@ -0,0 +1,44 @@
1
+ // packages/harness/src/rules/bookkeeping.ts - The `bookkeeping` rule: a changed path owned by a document (narrower
2
+ // glob wins) whose owning document is not in the same change set. etna's commit wall, ported as a change rule so the
3
+ // status check can block owned code merged without its document. A caller's `RuleWaiver` scoped to `bookkeeping` and
4
+ // naming the owning document drops the finding. docs/architecture/systems/harness-records.md speaks for it (Status
5
+ // check and enforcement).
6
+ import { waiverCovers } from './context.js';
7
+ import { owningDoc, scanOwningDocs } from './ownership.js';
8
+ /** The waiver kind a bookkeeping finding is covered by: the caller names the owning document in backticks. */
9
+ export const BOOKKEEPING_WAIVER_KIND = 'bookkeeping';
10
+ const touchedPaths = (change) => (change.status === 'R' && change.from ? [change.path, change.from] : [change.path]);
11
+ export function bookkeepingRule(ctx, docsRoot) {
12
+ const changes = ctx.changes ?? [];
13
+ if (changes.length === 0)
14
+ return [];
15
+ const inChangeSet = new Set(changes.flatMap(touchedPaths));
16
+ const docs = scanOwningDocs(ctx.head, docsRoot);
17
+ if (docs.length === 0)
18
+ return [];
19
+ const missing = new Map();
20
+ for (const change of changes) {
21
+ for (const path of touchedPaths(change)) {
22
+ if (path.startsWith(`${docsRoot}/`) || path.endsWith('.md'))
23
+ continue;
24
+ const owner = owningDoc(ctx.head, path, docs);
25
+ if (!owner)
26
+ continue;
27
+ const candidates = [owner.doc.doc, ...owner.tied.map((entry) => entry.doc)];
28
+ if (candidates.some((doc) => inChangeSet.has(doc)))
29
+ continue;
30
+ const paths = missing.get(owner.doc.doc) ?? [];
31
+ if (!paths.includes(path))
32
+ paths.push(path);
33
+ missing.set(owner.doc.doc, paths);
34
+ }
35
+ }
36
+ const findings = [];
37
+ for (const [doc, paths] of missing) {
38
+ if (waiverCovers(ctx.waivers ?? [], BOOKKEEPING_WAIVER_KIND, doc, ctx.today))
39
+ continue;
40
+ for (const path of paths)
41
+ findings.push({ id: 'bookkeeping.owner_not_updated', path, message: `${path} is owned by ${doc}, which this change does not update` });
42
+ }
43
+ return findings;
44
+ }
@@ -35,11 +35,18 @@ export function planGateRule(ctx, root) {
35
35
  }
36
36
  return findings;
37
37
  }
38
- /** Completion integrity for every gated plan at head: discharged ledgers, active Spec docs, their checklists, an Abandoned plan's backlog. */
38
+ /**
39
+ * Completion integrity for the gated plans at head: discharged ledgers, active Spec docs, their checklists, an Abandoned
40
+ * plan's backlog. With a change set, only the plans it adds or modifies are judged, so a change is never blocked by an
41
+ * older plan's debt it did not touch; with none, every plan at head is.
42
+ */
39
43
  export function ledgerRule(ctx, root) {
40
44
  const findings = [];
41
45
  const folder = plansFolder(root);
46
+ const touched = ctx.changes ? new Set(ctx.changes.filter((change) => change.status !== 'D').map((change) => change.path)) : null;
42
47
  for (const path of markdownIn(ctx.head, folder)) {
48
+ if (touched && !touched.has(path))
49
+ continue;
43
50
  const file = basenamePosix(path);
44
51
  const text = ctx.head.read(path) ?? '';
45
52
  if (!/^\*\*Gates:\*\*/m.test(text))
@@ -7,6 +7,7 @@ import { changelogEntryRule, changelogRule, frozenRecordRule, ledgerRule, planGa
7
7
  import { outputGateRule } from './outputGate.js';
8
8
  import { ownershipRule } from './ownership.js';
9
9
  import { packageTableRule } from './packageTable.js';
10
+ import { bookkeepingRule } from './bookkeeping.js';
10
11
  import { branchWorktreeRule, commitClaimRule, exitStopRule, foreignClaimRule, liveCommandRule, openDenialsRule, queueRule } from './sessionWalls.js';
11
12
  export * from './context.js';
12
13
  export { resolveDocsRoot, docsRootDeclaration, DEFAULT_DOCS_ROOT, DOCS_ROOT_SOURCES } from './docsRoot.js';
@@ -15,7 +16,9 @@ export { listPlans, openGatedPlans, planScopePaths, inPlanScope, undischargedRow
15
16
  export { changelogExemptFile, frozenReason, protectedListPath } from './changeRules.js';
16
17
  export { approvedPackages, rejectedPackages, APPROVED_TABLE_PATH } from './packageTable.js';
17
18
  export { AGENT_BRANCH_PREFIX, parseAgentRef } from './outputGate.js';
18
- /** Every rule id, append-only: an id is never renamed or removed. The last six delegate to other rules. */
19
+ export { BOOKKEEPING_WAIVER_KIND } from './bookkeeping.js';
20
+ export { PLAN_KEY_PATTERN, PLAN_KEY_SCAN_BYTES, formatPlanKey, readPlanKey, planKeyOf } from './planKey.js';
21
+ /** Every rule id, append-only: an id is never renamed or removed. Every id in RULE_ALIASES delegates to other rules. */
19
22
  export const RULE_IDS = [
20
23
  'ownership',
21
24
  'plan-gate',
@@ -39,6 +42,8 @@ export const RULE_IDS = [
39
42
  'file-gate',
40
43
  'bash-write-screen',
41
44
  'harness-checks',
45
+ 'bookkeeping',
46
+ 'bookkeeping-clean',
42
47
  ];
43
48
  export const isRuleId = (value) => RULE_IDS.includes(value);
44
49
  /** Seed ids naming a check another rule already makes: each runs its targets, whose findings keep their own ids. */
@@ -48,7 +53,8 @@ export const RULE_ALIASES = {
48
53
  'commit-gate': ['changelog'],
49
54
  'file-gate': ['plan-gate'],
50
55
  'bash-write-screen': ['frozen-record', 'protected-file'],
51
- 'harness-checks': ['ownership', 'ledger', 'changelog'],
56
+ 'harness-checks': ['ownership', 'ledger', 'changelog', 'bookkeeping'],
57
+ 'bookkeeping-clean': ['bookkeeping'],
52
58
  };
53
59
  const SESSION_RULES = ['branch-worktree', 'live-command', 'commit-claim', 'foreign-claim', 'open-denials', 'exit-stop', 'queue'];
54
60
  /** Change rules judge trees and a change set; session rules judge one session's action or facts, at the hook. */
@@ -63,6 +69,7 @@ const DIRECT_READS = {
63
69
  'changelog-entry': [...DOCS_SOURCES, '@docs/history/plans/**/*.md', '@docs/history/changelog/**/*.md'],
64
70
  'package-table': ['**/package.json', '.claude/hooks/approved-packages.md', '.claude/rules/general.md', '.claude/rules/testing.md'],
65
71
  'protected-file': [...DOCS_SOURCES, '@docs/operations/protected-files.md'],
72
+ bookkeeping: [...DOCS_SOURCES, '@docs/**/*.md'],
66
73
  };
67
74
  /** The path patterns each rule reads, docs-root-relative ones written `@docs/…`; a delegating id's is its targets' union. */
68
75
  export const RULE_READS = Object.fromEntries(RULE_IDS.map((id) => [id, [...new Set((RULE_ALIASES[id] ?? [id]).flatMap((target) => DIRECT_READS[target] ?? []))]]));
@@ -83,6 +90,7 @@ const RULES = {
83
90
  'open-denials': openDenialsRule,
84
91
  'exit-stop': exitStopRule,
85
92
  queue: queueRule,
93
+ bookkeeping: (ctx) => bookkeepingRule(ctx, docsRootOf(ctx)),
86
94
  };
87
95
  export class UnknownRuleError extends Error {
88
96
  constructor(id) {
@@ -0,0 +1,49 @@
1
+ // packages/harness/src/rules/planKey.ts - The plan key's one grammar: the header line `**Plan key:** <projectId>:<anchor>`
2
+ // a plan is written with at creation and never changes after, a rename included. Not a rule id; the status check, the
3
+ // CLI and the plan template writer read the same pattern. docs/architecture/systems/harness-records.md speaks for it
4
+ // (Record kinds, Status check and enforcement).
5
+ /**
6
+ * The key line. The anchor group restates ph07 Decision 4's plan anchor grammar (`^\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*$`)
7
+ * rather than importing `@nurix/contracts`, which the bundled CLI may not carry.
8
+ */
9
+ export const PLAN_KEY_PATTERN = /^\*\*Plan key:\*\* ([^\s:]{1,128}):(\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*)$/m;
10
+ /** How far into a plan the key is looked for: a header-bytes read of this many bytes parses as a full read does. */
11
+ export const PLAN_KEY_SCAN_BYTES = 4096;
12
+ const KEY_LINE = /^\*\*Plan key:\*\*/;
13
+ export function formatPlanKey(projectId, anchor) {
14
+ return `**Plan key:** ${projectId}:${anchor}`;
15
+ }
16
+ /** The newline-terminated lines that end within the first PLAN_KEY_SCAN_BYTES bytes, so a cut line never parses. */
17
+ function scannedLines(text) {
18
+ const bytes = new TextEncoder().encode(text);
19
+ const head = bytes.length > PLAN_KEY_SCAN_BYTES ? bytes.subarray(0, PLAN_KEY_SCAN_BYTES) : bytes;
20
+ const decoded = new TextDecoder('utf-8', { fatal: false }).decode(head);
21
+ const lastNewline = decoded.lastIndexOf('\n');
22
+ if (lastNewline === -1)
23
+ return [];
24
+ return decoded.slice(0, lastNewline).split('\n').map((line) => line.replace(/\r$/, ''));
25
+ }
26
+ /** The key the plan's header block declares, or null: no line, a malformed line, or two lines. */
27
+ export function readPlanKey(text) {
28
+ const lines = scannedLines(text);
29
+ const title = lines.findIndex((line) => /^# /.test(line));
30
+ const header = [];
31
+ for (const line of lines.slice(title + 1)) {
32
+ if (/^## /.test(line))
33
+ break;
34
+ header.push(line);
35
+ }
36
+ const keyLines = header.filter((line) => KEY_LINE.test(line));
37
+ if (keyLines.length !== 1)
38
+ return null;
39
+ const match = PLAN_KEY_PATTERN.exec(keyLines[0]);
40
+ return match ? { projectId: match[1], anchor: match[2] } : null;
41
+ }
42
+ /** The plan's key: the declared one, else `projectId` with the file name's anchor. */
43
+ export function planKeyOf(text, path, projectId) {
44
+ const declared = readPlanKey(text);
45
+ if (declared)
46
+ return { ...declared, isDeclared: true };
47
+ const file = path.slice(path.lastIndexOf('/') + 1).replace(/\.md$/, '');
48
+ return { projectId, anchor: file, isDeclared: false };
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/nustack",
3
- "version": "0.23.14",
3
+ "version": "0.23.16",
4
4
  "description": "The nustack CLI — bootstrap NuStack services into a repo via the NuStack discovery plane.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -34,9 +34,9 @@
34
34
  "@types/node": "^22.19.20",
35
35
  "typescript": "^5.7.2",
36
36
  "vitest": "^4.1.11",
37
- "@nurix/contracts": "2.0.1",
38
- "@nurix/nustack.studio": "0.0.0",
37
+ "@nurix/contracts": "2.0.2",
39
38
  "@nurix/harness": "0.0.0",
39
+ "@nurix/nustack.studio": "0.0.0",
40
40
  "@nurix/nustack.ui": "0.0.0"
41
41
  },
42
42
  "publishConfig": {