create-pmos 0.66.0 → 0.67.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.66.0",
3
+ "version": "0.67.1",
4
4
  "description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@c6198fe
1
+ kernel-source: pmos@7fc2d3a
2
2
  assembled: 2026-10-07
3
3
  mode: vendor
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Eval calibration log"
4
4
  description: "Append-only record of every PM override of an evaluator verdict."
5
- generated: {by: process:create-pmos, at: "2026-10-07T01:43:50Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T02:28:54Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Health budgets"
4
4
  description: "Closed registry of health-budget anchors (D59), edited only by the PM."
5
- generated: {by: process:create-pmos, at: "2026-10-07T01:43:50Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T02:28:54Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Discoveries and friction log"
4
4
  description: "Append-only record of what runs discovered and where the process rubbed."
5
- generated: {by: process:create-pmos, at: "2026-10-07T01:43:50Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T02:28:54Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -17,7 +17,7 @@ The top bar has these parts, from left to right:
17
17
  - **Branches and sessions** is in ⌘K. It opens a sheet with every branch of the repository (main first) and how many sessions run in each. Press a branch to see its sessions, open one, end one, or start a session there. **+ branch** makes a branch, and **+ session** starts one with a branch, an agent and a profile. With All repositories, the sheet lists every repository.
18
18
  - **Agenda, Initiatives, Board, Reports, Docs and People** are the six places. Press one to show it. The Agenda count shows how many items need you. Docs shows whether each document is the same as its Google Docs and Confluence copy. People shows the people you work with in the repository ([People](people.md)).
19
19
  - **Search or capture ⌘K** opens the command palette. Type to find an initiative, a repository, an open tab or a command.
20
- - **Sync** shows only when the current repository is not in sync with its remote. Press it to fetch, bring in the work of the other machine, record your changes and push. If the repository has not fetched in the last 10 minutes, Sync also shows, because a push from another machine is not visible until a fetch. When the repository fetched recently and is in sync, Sync is not shown. If Sync stops, the message names the step that stopped.
20
+ - **Sync** shows only when the current repository is not in sync with its remote. Press it to fetch, bring in the work of the other machine, record your changes and push. If the repository has not fetched in the last 10 minutes, Sync also shows, because a push from another machine is not visible until a fetch. When the repository fetched recently and is in sync, Sync is not shown. When two machines add lines to the same ledger (a `.jsonl` file under `pmos/` or `planning/`), Sync keeps the lines from both machines. If Sync stops, the message names the step that stopped. If both machines changed the same lines of another file, the message names the file. Keep the version you want in that file on one machine, sync it, then sync the other machine.
21
21
  - **Sync on its own.** If you forget to press Sync, the workspace syncs for you. It syncs each repository once when the workspace starts. It also syncs a repository that has records not yet pushed when no record changed for 5 minutes. In a document repository it syncs every changed file on any branch. In a code repository it syncs only on the default branch (for example `main`), and only when every commit not yet pushed changes records and no code. If you close the tab while records are not yet pushed, the browser asks you first. To turn automatic sync off, start the workspace with `PMOS_AUTO_SYNC=0`.
22
22
  - **Settings** opens the settings of the workspace.
23
23
 
@@ -54,6 +54,26 @@ const isRecord = (rel) => RECORD_PATHS.some((p) => rel === p || rel.startsWith(`
54
54
  // PM repositories append ledgers from several machines; `merge=union` keeps both sides' lines instead
55
55
  // of conflicting (D70). Added once, committed with the records. Never touches a product-code repository.
56
56
  const UNION_LINE = '*.jsonl merge=union';
57
+
58
+ // The same rule, given to the replay itself rather than read from the repository. A committed `.gitattributes` holds
59
+ // only for a PM-profile repository, and only once it is already on the remote: a rebase reads the attributes of the
60
+ // tree it replays ONTO, so a union line arriving in the very commit being replayed does not apply to it. A code
61
+ // repository never carries the line at all, so two machines appending pmos/state/runs.jsonl stopped Sync at
62
+ // integrate every time. `core.attributesFile` is the lowest-precedence attribute source, so a repository's own
63
+ // `.gitattributes` still wins; it is set for this one rebase and written to no tracked file.
64
+ // A code repository unions its record ledgers only; a document repository every ledger.
65
+ function unionAttributesFor(root) {
66
+ const lines = profileOf(root) === 'pm-artifacts' ? [UNION_LINE]
67
+ : RECORD_PATHS.filter((p) => !p.startsWith('.')).map((p) => `${p}/**/*.jsonl merge=union`);
68
+ const f = path.join(os.tmpdir(), `pmos-sync-attributes-${profileOf(root)}`);
69
+ const text = `${lines.join('\n')}\n`;
70
+ try {
71
+ // written only when it differs, so a second repository's sync never reads it half-written
72
+ if ((fs.existsSync(f) ? fs.readFileSync(f, 'utf8') : '') !== text) fs.writeFileSync(f, text);
73
+ return f;
74
+ } catch { return null; }
75
+ }
76
+
57
77
  function ensureUnionAttribute(root) {
58
78
  try {
59
79
  const prof = fs.readFileSync(path.join(root, '.pmos-profile'), 'utf8').split('\n').map((l) => l.replace(/#.*$/, '').trim()).find(Boolean);
@@ -136,11 +156,19 @@ async function sync(root, { label } = {}) {
136
156
  const s2 = await status(root);
137
157
  if (s2.behind) {
138
158
  // Fast-forward when we have nothing of our own; otherwise replay OUR unpushed commits on top.
139
- const verb = s2.ahead ? ['rebase', '--autostash', s2.upstream] : ['merge', '--ff-only', s2.upstream];
159
+ const attrs = s2.ahead ? unionAttributesFor(root) : null;
160
+ const verb = s2.ahead ? [...(attrs ? ['-c', `core.attributesFile=${attrs}`] : []), 'rebase', '--autostash', s2.upstream]
161
+ : ['merge', '--ff-only', s2.upstream];
140
162
  const r = await run(root, verb);
141
163
  if (!r.ok) {
142
- if (s2.ahead) await run(root, ['rebase', '--abort']);
143
- return stop('integrate', `${s2.ahead ? 'the two machines changed the same lines — rebase aborted, nothing changed' : 'cannot fast-forward'} — ${r.err.split('\n')[0] || 'see git'}`);
164
+ if (!s2.ahead) return stop('integrate', `cannot fast-forward — ${r.err.split('\n')[0] || 'see git'}`);
165
+ // Name the files before the abort clears them: the PM has to open these, and "the same lines" alone says not where.
166
+ const u = await run(root, ['diff', '--name-only', '--diff-filter=U']);
167
+ const files = u.ok && u.out ? u.out.split('\n').filter(Boolean) : [];
168
+ await run(root, ['rebase', '--abort']);
169
+ return stop('integrate', files.length
170
+ ? `both machines changed ${files.join(', ')} — rebase aborted, nothing changed; keep the version you want in each file on one machine, sync it, then sync the other`
171
+ : `the two machines changed the same lines — rebase aborted, nothing changed — ${r.err.split('\n')[0] || 'see git'}`);
144
172
  }
145
173
  done('integrate', s2.ahead ? `replayed ${s2.ahead} local commit(s) on the remote` : `fast-forwarded ${s2.behind} commit(s)`);
146
174
  } else done('integrate', 'nothing to bring in');
@@ -183,5 +211,5 @@ async function defaultBranchOf(root, remote) {
183
211
  return null;
184
212
  }
185
213
 
186
- module.exports = { status, sync, ensureUnionAttribute, RECORD_PATHS, isRecord, REMOTE_FRESH_MS,
214
+ module.exports = { status, sync, ensureUnionAttribute, unionAttributesFor, RECORD_PATHS, isRecord, REMOTE_FRESH_MS,
187
215
  unpushedPaths, newestDirtyRecordMs, defaultBranchOf, profileOf };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.66.0",
3
+ "version": "0.67.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.66.0",
3
+ "version": "0.67.1",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -2,8 +2,9 @@
2
2
  'use strict';
3
3
  /* run-tests.js — run the workspace suites side by side.
4
4
  *
5
- * node run-tests.js <group> [--jobs=N] [--shard=I/N] [--changed[=REF]] [--list] [--verbose]
5
+ * node run-tests.js <group> [--jobs=N] [--shard=I/N] [--changed[=REF]] [--timings-out=FILE] [--list] [--verbose]
6
6
  * node run-tests.js --check
7
+ * node run-tests.js --merge-timings=DIR
7
8
  *
8
9
  * Groups come from test-files.json: unit (test.js + modules), modules, panels, ui, browser
9
10
  * (panels + ui), all. The npm scripts call this file, so `npm test` and CI run the same lists.
@@ -31,6 +32,14 @@
31
32
  * test-files.json, plus `helpers` (a -test.js file that is a helper, not a suite, with the reason, as
32
33
  * in ELSEWHERE). This file merges every fragment into the lists before it does anything else, so
33
34
  * --check, --shard and every group see one list. A fragment that is not valid fails every command.
35
+ *
36
+ * Timings. --shard balances the shards by each suite's time, and the shards only finish together when
37
+ * those times are the CI runner's. test-timings.json holds times measured on CI; the `seconds` keys in
38
+ * test-files.json and the fragments are the fallback for a suite it does not name yet, and a suite in
39
+ * neither counts as 10. The `seconds` numbers were measured on a 4-CPU sandbox and were off by up to
40
+ * 4x on the 2-CPU runners, which left one shard running 280s while two others finished in 130s.
41
+ * Every CI job writes its times with --timings-out and uploads them; --merge-timings=DIR folds a
42
+ * downloaded set into test-timings.json. A stale time never skips a suite: it only moves it.
34
43
  */
35
44
  const fs = require('fs');
36
45
  const os = require('os');
@@ -98,7 +107,10 @@ const args = process.argv.slice(2);
98
107
  const flag = (name) => { const a = args.find((x) => x === `--${name}` || x.startsWith(`--${name}=`)); return a === undefined ? undefined : (a.includes('=') ? a.slice(a.indexOf('=') + 1) : true); };
99
108
  const group = args.find((a) => !a.startsWith('--'));
100
109
 
101
- const secs = (f) => Number((FILES.seconds || {})[f]) || 10;
110
+ const TIMINGS_FILE = path.join(DIR, 'test-timings.json');
111
+ let TIMINGS = {};
112
+ try { TIMINGS = JSON.parse(fs.readFileSync(TIMINGS_FILE, 'utf8')).seconds || {}; } catch { /* no CI timings yet: the seconds keys decide */ }
113
+ const secs = (f) => Number(TIMINGS[f]) || Number((FILES.seconds || {})[f]) || 10;
102
114
  // Slowest first, so a long suite never starts last and holds the run open on its own.
103
115
  const bySlowest = (list) => list.map((f, k) => ({ f, k })).sort((a, b) => secs(b.f) - secs(a.f) || a.k - b.k).map((x) => x.f);
104
116
 
@@ -155,6 +167,7 @@ function changedSuites(list, ref) {
155
167
  for (const f of files) {
156
168
  if (source.has(f)) { picked.add(f); continue; }
157
169
  if (/^(guide|docs)\/|\.md$/.test(f)) continue; // prose: no suite asserts on it except guide.test.js, a unit suite
170
+ if (f === 'test-timings.json') continue; // run order only: no suite reads it
158
171
  const stem = path.basename(f).replace(/\.[^.]+$/, '');
159
172
  const key = stem.replace(/^(panel|view|tab)-/, '');
160
173
  const hits = list.filter((s) => source.get(s).includes(stem) || s.includes(`-${key}-`) || s.includes(`-${key}.`));
@@ -207,8 +220,30 @@ function runOne(file, verbose) {
207
220
  });
208
221
  }
209
222
 
223
+ // Fold the --timings-out files a CI run uploaded (one per job, in DIR) into test-timings.json. A suite
224
+ // the run did not reach keeps its old time; a time under one second is recorded as one.
225
+ function mergeTimings(dir) {
226
+ const old = fs.existsSync(TIMINGS_FILE) ? JSON.parse(fs.readFileSync(TIMINGS_FILE, 'utf8')) : {};
227
+ const seconds = { ...(old.seconds || {}) };
228
+ let n = 0;
229
+ for (const name of fs.readdirSync(dir, { recursive: true }).filter((x) => String(x).endsWith('.json'))) {
230
+ for (const [f, v] of Object.entries(JSON.parse(fs.readFileSync(path.join(dir, String(name)), 'utf8')))) {
231
+ if (typeof v === 'number' && GROUPS.all.includes(f)) { seconds[f] = Math.max(1, Math.round(v)); n++; }
232
+ }
233
+ }
234
+ const sorted = Object.fromEntries(Object.keys(seconds).filter((f) => GROUPS.all.includes(f)).sort().map((f) => [f, seconds[f]]));
235
+ const out = {
236
+ _comment: old._comment || 'Wall time of each suite on the Workspace CI runners, in seconds. run-tests.js --shard balances the shards with these numbers. Refresh: download the timings-* artifacts of a green Workspace CI run into one directory, then run `node run-tests.js --merge-timings=DIR`.',
237
+ measured: new Date().toISOString().slice(0, 10),
238
+ seconds: sorted,
239
+ };
240
+ fs.writeFileSync(TIMINGS_FILE, `${JSON.stringify(out, null, 2)}\n`);
241
+ console.log(`run-tests: ${n} times merged into test-timings.json (${Object.keys(sorted).length} suites).`);
242
+ }
243
+
210
244
  async function main() {
211
245
  if (flag('check')) return check();
246
+ if (flag('merge-timings')) return mergeTimings(flag('merge-timings'));
212
247
  if (fragProblems.length) { for (const p of fragProblems) console.error(`✗ ${p}`); process.exitCode = 2; return; }
213
248
  if (!group || !GROUPS[group]) {
214
249
  console.error(`usage: node run-tests.js <${Object.keys(GROUPS).join('|')}> [--jobs=N] [--shard=I/N] [--list] [--verbose]\n node run-tests.js --check`);
@@ -250,6 +285,8 @@ async function main() {
250
285
 
251
286
  const failed = results.filter((r) => !r.ok);
252
287
  const slowest = [...results].sort((a, b) => b.secs - a.secs).slice(0, 5).map((r) => `${r.file} ${r.secs}s`).join(', ');
288
+ const timingsOut = flag('timings-out');
289
+ if (timingsOut && timingsOut !== true) fs.writeFileSync(timingsOut, JSON.stringify(Object.fromEntries(results.map((r) => [r.file, r.secs])), null, 2));
253
290
  console.log(`\nrun-tests: ${results.length - failed.length}/${results.length} suites passed in ${((Date.now() - t0) / 1000).toFixed(1)}s wall. Slowest: ${slowest}.`);
254
291
  // exitCode, never process.exit(): stdout to a pipe is asynchronous, and exiting here cut the CI log
255
292
  // off mid-suite, before the summary that names what failed.
@@ -0,0 +1,172 @@
1
+ {
2
+ "_comment": "Wall time of each suite on the Workspace CI runners, in seconds. run-tests.js --shard balances the shards with these numbers. Refresh: download the timings-* artifacts of a green Workspace CI run into one directory, then run `node run-tests.js --merge-timings=DIR`.",
3
+ "measured": "2026-10-07",
4
+ "seconds": {
5
+ "agenda.test.js": 1,
6
+ "auto-sync.test.js": 2,
7
+ "board-cli.test.js": 3,
8
+ "capture.test.js": 1,
9
+ "claims.test.js": 2,
10
+ "cuts.test.js": 1,
11
+ "d86-seams.test.js": 1,
12
+ "day-run.test.js": 21,
13
+ "doc-category-corrections.test.js": 1,
14
+ "doc-category.test.js": 1,
15
+ "doc-library.test.js": 1,
16
+ "doc-sync.test.js": 1,
17
+ "docs-cli.test.js": 5,
18
+ "external-linking.test.js": 1,
19
+ "factory-join.test.js": 1,
20
+ "first-paint-board.test.js": 8,
21
+ "first-paint-count.test.js": 23,
22
+ "first-paint-initiatives.test.js": 12,
23
+ "first-paint-reports.test.js": 8,
24
+ "first-paint-today.test.js": 26,
25
+ "fixture-parity.test.mjs": 1,
26
+ "focus.test.js": 1,
27
+ "glossary.test.js": 1,
28
+ "goals.test.js": 1,
29
+ "grant-mint.test.js": 1,
30
+ "guide.test.js": 1,
31
+ "handoff.test.js": 1,
32
+ "health.test.js": 1,
33
+ "initiative-cli.test.js": 8,
34
+ "initiative-needs.test.js": 2,
35
+ "initiative-surface.test.js": 3,
36
+ "initiative-writer.test.js": 1,
37
+ "issues.test.js": 1,
38
+ "item-cli.test.js": 7,
39
+ "item-spans.test.js": 1,
40
+ "jev-categorize.test.js": 1,
41
+ "jev-claims.test.js": 1,
42
+ "jev-route.test.js": 1,
43
+ "jev-status.test.js": 1,
44
+ "jira-wbs.test.js": 1,
45
+ "kg.test.js": 2,
46
+ "kit-update.test.js": 1,
47
+ "launcher.test.js": 1,
48
+ "learn.test.js": 4,
49
+ "live-notes.test.js": 5,
50
+ "markdown.test.mjs": 1,
51
+ "measurements.test.js": 1,
52
+ "next-step-actions.test.js": 11,
53
+ "no-network.test.js": 1,
54
+ "notes-listen.test.js": 2,
55
+ "notes.test.js": 5,
56
+ "online-mode.test.js": 1,
57
+ "people.test.js": 2,
58
+ "phone-access.test.js": 1,
59
+ "phone-command.test.js": 4,
60
+ "placements.test.js": 1,
61
+ "plan-items-stage-parity.test.js": 1,
62
+ "plan-items.test.js": 1,
63
+ "plan-routes.test.js": 11,
64
+ "plan-tree.test.js": 6,
65
+ "pm-board-dest-phone-test.js": 14,
66
+ "pm-board-dest-test.js": 27,
67
+ "pm-breakdown-phone-test.js": 33,
68
+ "pm-breakdown-test.js": 68,
69
+ "pm-breakdown-timeline-test.js": 25,
70
+ "pm-cuts-test.js": 16,
71
+ "pm-detail-test.js": 18,
72
+ "pm-doc-category-test.js": 6,
73
+ "pm-docs-test.js": 37,
74
+ "pm-frame-phone-test.js": 14,
75
+ "pm-frame-test.js": 21,
76
+ "pm-initiatives-dest-phone-test.js": 9,
77
+ "pm-initiatives-dest-test.js": 28,
78
+ "pm-initiatives-test.js": 19,
79
+ "pm-initiatives-timeline-test.js": 39,
80
+ "pm-jira-test.js": 6,
81
+ "pm-lines-needs.test.js": 1,
82
+ "pm-live-notes-test.js": 25,
83
+ "pm-modes-test.js": 11,
84
+ "pm-nonce-test.js": 14,
85
+ "pm-notes-test.js": 40,
86
+ "pm-palette-test.js": 29,
87
+ "pm-people-test.js": 12,
88
+ "pm-r1-test.js": 10,
89
+ "pm-r10-test.js": 1,
90
+ "pm-r11-test.js": 2,
91
+ "pm-r12-test.js": 17,
92
+ "pm-r13-test.js": 12,
93
+ "pm-r14-test.js": 7,
94
+ "pm-r15-test.js": 7,
95
+ "pm-r16-test.js": 16,
96
+ "pm-r17-test.js": 7,
97
+ "pm-r18-test.js": 11,
98
+ "pm-r19-test.js": 20,
99
+ "pm-r2-test.js": 7,
100
+ "pm-r20-test.js": 19,
101
+ "pm-r21-test.js": 29,
102
+ "pm-r22-test.js": 2,
103
+ "pm-r23-test.js": 17,
104
+ "pm-r24-test.js": 15,
105
+ "pm-r25-test.js": 1,
106
+ "pm-r26-test.js": 1,
107
+ "pm-r27-test.js": 26,
108
+ "pm-r28-test.js": 1,
109
+ "pm-r29-filespad-test.js": 1,
110
+ "pm-r29-knowledge-test.js": 1,
111
+ "pm-r29-memory-test.js": 1,
112
+ "pm-r29-newsession-alignment-test.js": 1,
113
+ "pm-r29-portfolio-test.js": 1,
114
+ "pm-r29-reports-addrepo-test.js": 1,
115
+ "pm-r29-skills-scheduled-test.js": 1,
116
+ "pm-r29-today-test.js": 12,
117
+ "pm-r29-validate-guide-test.js": 1,
118
+ "pm-r3-test.js": 11,
119
+ "pm-r30-test.js": 13,
120
+ "pm-r4-test.js": 4,
121
+ "pm-r5-test.js": 6,
122
+ "pm-r6-test.js": 5,
123
+ "pm-r7-test.js": 1,
124
+ "pm-r8-test.js": 1,
125
+ "pm-r9-test.js": 10,
126
+ "pm-report-slides-test.js": 6,
127
+ "pm-reports-phone-test.js": 9,
128
+ "pm-reports-test.js": 20,
129
+ "pm-repos-test.js": 10,
130
+ "pm-seams-test.js": 16,
131
+ "pm-shell-test.js": 6,
132
+ "pm-tabs-test.js": 55,
133
+ "pm-term-test.js": 38,
134
+ "pm-today-goals-test.js": 30,
135
+ "pm-today-phone-test.js": 27,
136
+ "pm-today-test.js": 60,
137
+ "pm-updates-test.js": 16,
138
+ "proactive-prepare.test.js": 12,
139
+ "proactive.test.js": 17,
140
+ "profiles.test.js": 1,
141
+ "public-host.test.js": 1,
142
+ "r25-guards.test.js": 9,
143
+ "r3-display.test.js": 4,
144
+ "replaced-tests.test.js": 17,
145
+ "repo-scan.test.js": 1,
146
+ "repo-watch.test.js": 1,
147
+ "report-cli.test.js": 2,
148
+ "report-draft.test.js": 4,
149
+ "report-needs.test.js": 1,
150
+ "report-slides-data.test.js": 1,
151
+ "routes-board.test.js": 1,
152
+ "routes-capture.test.js": 1,
153
+ "routes-initiatives.test.js": 3,
154
+ "routes-reports.test.js": 1,
155
+ "routes-timeline.test.js": 1,
156
+ "routes-today.test.js": 6,
157
+ "scan-consistency.test.js": 1,
158
+ "schedule-cli.test.js": 3,
159
+ "session-flag.test.js": 1,
160
+ "start-policy.test.js": 1,
161
+ "state-isolation.test.js": 37,
162
+ "status-facts-panel.test.js": 1,
163
+ "story-artifacts.test.js": 1,
164
+ "sync.test.js": 4,
165
+ "test.js": 65,
166
+ "today.test.js": 5,
167
+ "tracker-refresh.test.js": 1,
168
+ "typesafe-validation.test.js": 1,
169
+ "ui-test.js": 43,
170
+ "work-check-needs.test.js": 1
171
+ }
172
+ }