@kitkat-vn/kitcode 0.2.0-staging.4 → 0.2.0-staging.6
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/bin/kitcode.mjs +174 -5
- package/package.json +2 -2
- package/src/api.mjs +22 -1
- package/src/diagnostics.mjs +872 -0
- package/src/hook-prompt.mjs +13 -0
- package/src/integration-spec.mjs +29 -17
- package/src/onboarding-electron.mjs +8 -0
- package/src/onboarding-window.mjs +47 -7
- package/src/runtime.mjs +11 -6
package/src/hook-prompt.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {spawn} from 'node:child_process';
|
|
3
|
+
import {hookFinished, hookStarted} from './diagnostics.mjs';
|
|
3
4
|
import {appendLogLine} from './log-file.mjs';
|
|
4
5
|
import {formatBreakDuration, nextBreakReminder} from './next-break-reminder.mjs';
|
|
5
6
|
import {getDiskRewardSummary, getReadyUnannouncedTiers, markTiersAnnounced} from './reward.mjs';
|
|
@@ -175,11 +176,20 @@ function collectPromptProgress(source) {
|
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
|
|
179
|
+
// In a tester build alone (src/diagnostics.mjs), each run also leaves a start marker, which it removes as it ends, so that a
|
|
180
|
+
// run its agent stopped at the timeout is found later by whatever sends, since it cannot record its own end; and it records
|
|
181
|
+
// a failure, by the error's code and name, or else a run past 2 s: one event at most, one small file. The hook never touches
|
|
182
|
+
// the network: a tester build's tracker, or a command, sends what it recorded. Neither call ever throws; in any other build
|
|
183
|
+
// each returns at once.
|
|
178
184
|
export async function runPromptHook({source}) {
|
|
179
185
|
if (isDisabled()) {
|
|
180
186
|
return;
|
|
181
187
|
}
|
|
182
188
|
|
|
189
|
+
const started = Date.now();
|
|
190
|
+
const marker = hookStarted(source, started);
|
|
191
|
+
let failure = null;
|
|
192
|
+
|
|
183
193
|
try {
|
|
184
194
|
const input = await readStdin();
|
|
185
195
|
const payload = JSON.parse(input || '{}');
|
|
@@ -199,6 +209,9 @@ export async function runPromptHook({source}) {
|
|
|
199
209
|
notify('KitCode milestone reached', `Milestone ${label} unlocked. Open kitcode dashboard.`);
|
|
200
210
|
process.stdout.write(`${JSON.stringify(hookOutputFor({tiers: announced, reward}))}\n`);
|
|
201
211
|
} catch (error) {
|
|
212
|
+
failure = error;
|
|
202
213
|
logHookError(error);
|
|
214
|
+
} finally {
|
|
215
|
+
hookFinished(marker, source, Date.now() - started, failure);
|
|
203
216
|
}
|
|
204
217
|
}
|
package/src/integration-spec.mjs
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
export const CLI_PACKAGE_NAME = '@kitkat-vn/kitcode';
|
|
2
2
|
export const CLI_PACKAGE_COMMAND = `npx ${CLI_PACKAGE_NAME}`;
|
|
3
3
|
export const CLI_GLOBAL_INSTALL_COMMAND = `npm install -g ${CLI_PACKAGE_NAME}`;
|
|
4
|
-
// A staging round's tester build,
|
|
4
|
+
// A staging round's tester build, published under npm's `staging` tag: since 2026-10-02 the only way a tester installs it
|
|
5
|
+
// (stagingSetupPromptFor, below).
|
|
5
6
|
export const CLI_STAGING_INSTALL_COMMAND = `${CLI_GLOBAL_INSTALL_COMMAND}@staging`;
|
|
6
7
|
export const CLI_GLOBAL_COMMAND = 'kitcode';
|
|
7
8
|
export const DASHBOARD_URL = "https://staging.kitcode.vn/";
|
|
9
|
+
// Where a tester build sends KitCode's own diagnostics (src/diagnostics.mjs; docs/backend.md, "Tester diagnostics"): the
|
|
10
|
+
// staging dashboard's /v1/diagnostics, which scripts/tester-build.mjs alone writes here, as it writes DASHBOARD_URL. null in
|
|
11
|
+
// the repository, and so in every release of it: such a CLI collects, writes and sends no diagnostics at all.
|
|
12
|
+
export const DIAGNOSTICS_URL = "https://staging.kitcode.vn/v1/diagnostics";
|
|
8
13
|
export const LOCAL_SERVER_URL = 'http://127.0.0.1:4747';
|
|
9
14
|
export const STATE_DISPLAY_PATH = '~/.kitcode/state.json';
|
|
10
15
|
export const RUNNER_PATH_PARTS = ['.kitcode', 'bin', 'kitcode'];
|
|
@@ -258,16 +263,28 @@ export function assistantSetupPromptFor(source) {
|
|
|
258
263
|
].join('\n');
|
|
259
264
|
}
|
|
260
265
|
|
|
266
|
+
// What the staging setup prompt says of the tester build's diagnostics (src/diagnostics.mjs; the operator's decision 2 of
|
|
267
|
+
// the voucher scheme's phase 4, 2026-10-01): that it sends them, where to, and what they never hold. In the tester's own
|
|
268
|
+
// voice, as the prompt is, and with no URL in it, as the prompt has none.
|
|
269
|
+
export const STAGING_DIAGNOSTICS_NOTICE = 'The tester build sends diagnostic logs to KitCode\'s staging server: KitCode\'s own events and errors, such as the tracker starting or a hook failing, and never my code, my files, my prompts or my project names.';
|
|
270
|
+
|
|
261
271
|
// The setup prompt a staging dashboard copies for its invited testers (apps/web src/lib/staging-prompts.ts): the one
|
|
262
|
-
// above, line for line, with
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
// above, line for line, with four changes and no other. Step 2 installs the round's tester build from npm's `staging` tag,
|
|
273
|
+
// and checks its version. Then, before step 3 turns KitCode on, it stops a tracker an earlier build left running. It holds
|
|
274
|
+
// no URL: the public docs the prompt above links are not public yet (docs/lessons-learned.md F5). And, since the voucher
|
|
275
|
+
// scheme's phase 4, it says, under the round's line, that the tester build sends diagnostic logs to KitCode's staging
|
|
276
|
+
// server (STAGING_DIAGNOSTICS_NOTICE). It is made from the prompt above as it stands, so that the two cannot drift: a line
|
|
277
|
+
// it changes that is not there once throws, and check-integration-spec.mjs runs both.
|
|
278
|
+
//
|
|
279
|
+
// Until 2026-10-02 it could install a file the tester downloaded from the staging site instead (install 'file', with its
|
|
280
|
+
// fileName). The operator's rule of that day is npm alone, "never let user download file even as tester, must be via npm
|
|
281
|
+
// package only": it takes the tester build's version and nothing else, and refuses an option it does not know, so that a
|
|
282
|
+
// caller still asking for the file is told so rather than given npm in silence.
|
|
283
|
+
export function stagingSetupPromptFor(source, options) {
|
|
284
|
+
const {testerVersion, ...others} = options;
|
|
285
|
+
|
|
286
|
+
if (Object.keys(others).length > 0) {
|
|
287
|
+
throw new Error(`stagingSetupPromptFor takes the tester build's version alone, and installs it from npm's staging tag: it does not know ${Object.keys(others).join(', ')}.`);
|
|
271
288
|
}
|
|
272
289
|
|
|
273
290
|
const lines = assistantSetupPromptFor(source).split('\n');
|
|
@@ -289,15 +306,10 @@ export function stagingSetupPromptFor(source, {testerVersion, fileName, install}
|
|
|
289
306
|
throw new Error('stagingSetupPromptFor found no blank line after the public docs: assistantSetupPromptFor changed, and stagingSetupPromptFor must follow it.');
|
|
290
307
|
}
|
|
291
308
|
|
|
292
|
-
lines.splice(docs, docsEnd - docs, `KitCode is a local-first project. I am an invited tester of its staging round, which needs KitCode's tester build, ${testerVersion}: step 2 installs it
|
|
309
|
+
lines.splice(docs, docsEnd - docs, `KitCode is a local-first project. I am an invited tester of its staging round, which needs KitCode's tester build, ${testerVersion}: step 2 installs it.`, STAGING_DIAGNOSTICS_NOTICE);
|
|
293
310
|
|
|
294
311
|
const installLine = onlyLine((line) => line.endsWith(`install KitCode globally with: ${CLI_GLOBAL_INSTALL_COMMAND}`), 'the install');
|
|
295
|
-
lines[installLine] = lines[installLine].replace(
|
|
296
|
-
`with: ${CLI_GLOBAL_INSTALL_COMMAND}`,
|
|
297
|
-
install === 'npm'
|
|
298
|
-
? `with: ${CLI_STAGING_INSTALL_COMMAND}`
|
|
299
|
-
: `from the tester build I downloaded from KitCode's staging site, ${fileName}. Look for it in my Downloads folder, and if it is not there, ask me where it is. In the folder that holds it, run: npm install -g ./${fileName}`,
|
|
300
|
-
);
|
|
312
|
+
lines[installLine] = lines[installLine].replace(`with: ${CLI_GLOBAL_INSTALL_COMMAND}`, `with: ${CLI_STAGING_INSTALL_COMMAND}`);
|
|
301
313
|
|
|
302
314
|
const globalCommand = onlyLine((line) => line === `- After install, use the global ${CLI_GLOBAL_COMMAND} command for all following steps (not npx).`, 'the global command');
|
|
303
315
|
lines.splice(
|
|
@@ -4,6 +4,7 @@ import {app, BrowserWindow, dialog, ipcMain, nativeTheme} from 'electron';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
import {describeProjects, listProjectRecords, registerNewProjects, removeProject, resolveInitialProjectSuggestion} from './runtime.mjs';
|
|
7
|
+
import {diagnosticsEnabled, readInvitedEmail, setInvitedEmail} from './diagnostics.mjs';
|
|
7
8
|
import {DASHBOARD_URL} from './integration-spec.mjs';
|
|
8
9
|
import {onboardingPreferences, saveOnboardingPreferences, STORE_DIR} from './store.mjs';
|
|
9
10
|
import {resolveSetupPlatform} from './onboarding-platform.mjs';
|
|
@@ -154,6 +155,8 @@ app.whenReady().then(() => {
|
|
|
154
155
|
...onboardingPreferences(),
|
|
155
156
|
projects: listProjectRecords(),
|
|
156
157
|
suggestedProjects: initialSuggestedProjects(),
|
|
158
|
+
// A tester build's: the email the tester gave before, if any (src/diagnostics.mjs).
|
|
159
|
+
...(diagnosticsEnabled() ? {invitedEmail: readInvitedEmail()} : {}),
|
|
157
160
|
};
|
|
158
161
|
});
|
|
159
162
|
ipcMain.handle('kitcode:onboarding-select-folders', async (event) => {
|
|
@@ -164,6 +167,11 @@ app.whenReady().then(() => {
|
|
|
164
167
|
});
|
|
165
168
|
ipcMain.handle('kitcode:onboarding-submit', async (event, input) => {
|
|
166
169
|
if (event.sender !== window.webContents) return {ok: false, error: 'Invalid setup request.'};
|
|
170
|
+
// A tester build's invited email, kept outside state.json, before anything else is saved; one that is not an address is
|
|
171
|
+
// asked for again, and nothing is saved.
|
|
172
|
+
if (diagnosticsEnabled() && typeof input?.invitedEmail === 'string' && !setInvitedEmail(input.invitedEmail).ok) {
|
|
173
|
+
return {ok: false, error: 'That email address does not look right: check it, or leave it empty.'};
|
|
174
|
+
}
|
|
167
175
|
const folders = validFolders(input?.folders);
|
|
168
176
|
if (!folders.length && !listProjectRecords().length) return {ok: false, error: 'Add at least one readable project folder.'};
|
|
169
177
|
try { registerNewProjects(folders); } catch { return {ok: false, error: 'KitCode could not register one of those folders.'}; }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {diagnosticsEnabled} from './diagnostics.mjs';
|
|
1
2
|
import {
|
|
2
3
|
renderPlatformChrome,
|
|
3
4
|
resolveSetupPlatform,
|
|
@@ -5,7 +6,11 @@ import {
|
|
|
5
6
|
} from './onboarding-platform.mjs';
|
|
6
7
|
import {contentSecurityPolicy, fontFaces} from './window-page.mjs';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// `diagnostics`: whether this is a tester build (src/diagnostics.mjs), unless a test says. Then, and only then, Welcome says
|
|
10
|
+
// that the build sends diagnostic logs to KitCode's staging server, and what they never hold, and asks, optionally, for the
|
|
11
|
+
// email the tester was invited with, so that staff can find their logs by it. Without it the page is as it was, byte for
|
|
12
|
+
// byte.
|
|
13
|
+
export function renderOnboardingWindow(platform = resolveSetupPlatform(), {diagnostics = diagnosticsEnabled()} = {}) {
|
|
9
14
|
const theme = setupPlatformTheme(platform);
|
|
10
15
|
// The page's one stylesheet and one script, kept apart so that its policy allows exactly them.
|
|
11
16
|
const style = `
|
|
@@ -460,7 +465,28 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform()) {
|
|
|
460
465
|
.status-text[data-state="success"] { color: var(--red-color); }
|
|
461
466
|
|
|
462
467
|
.keyboard-hint.status-shortcuts { color: var(--white-text); }
|
|
463
|
-
|
|
468
|
+
${diagnostics ? `
|
|
469
|
+
/* With a third step the steps outgrow the panel at the window's own 960 x 640: the projects step must not shrink, as it
|
|
470
|
+
does alone to fill the panel, or its list and + ADD PROJECTS spill under the diagnostics step, whose email field then
|
|
471
|
+
takes their clicks (found 2026-10-02). The panel scrolls instead, as its rail expects. */
|
|
472
|
+
.projects-step { flex: 0 0 auto; }
|
|
473
|
+
|
|
474
|
+
.diagnostics-step p { color: var(--white-text); font-size: 10px; line-height: 1.5; margin-bottom: 10px; max-width: 560px; }
|
|
475
|
+
|
|
476
|
+
.diagnostics-step label { display: block; color: var(--grey-text); font-size: 10px; margin-bottom: 6px; }
|
|
477
|
+
|
|
478
|
+
.diagnostics-step input {
|
|
479
|
+
width: 100%;
|
|
480
|
+
max-width: 360px;
|
|
481
|
+
padding: 7px 9px;
|
|
482
|
+
background: transparent;
|
|
483
|
+
color: var(--white-text);
|
|
484
|
+
border: 1px dashed var(--grey-border);
|
|
485
|
+
-webkit-app-region: no-drag;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.diagnostics-step input:focus-visible { outline: none; border-color: var(--red-color); }
|
|
489
|
+
` : ''} `;
|
|
464
490
|
const script = `
|
|
465
491
|
const bridge = window.kitcodeOnboarding;
|
|
466
492
|
const projectList = document.getElementById('projectList');
|
|
@@ -472,7 +498,8 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform()) {
|
|
|
472
498
|
const setupContent = document.getElementById('setupContent');
|
|
473
499
|
const railItems = [...document.querySelectorAll('.rail-item')];
|
|
474
500
|
const stepSections = [...document.querySelectorAll('[data-setup-step]')];
|
|
475
|
-
const SAVE_LABEL = '+ SAVE AND OPEN KITCODE ->'
|
|
501
|
+
const SAVE_LABEL = '+ SAVE AND OPEN KITCODE ->';${diagnostics ? `
|
|
502
|
+
const invitedEmail = document.getElementById('invitedEmail');` : ''}
|
|
476
503
|
const FOLDER_ICON = '<svg class="dropzone-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>';
|
|
477
504
|
let persistedProjects = [];
|
|
478
505
|
let pendingProjects = [];
|
|
@@ -713,7 +740,8 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform()) {
|
|
|
713
740
|
const result = await bridge.submit({
|
|
714
741
|
folders: pendingProjects.map((project) => project.repoRoot),
|
|
715
742
|
autoTrack,
|
|
716
|
-
companionView
|
|
743
|
+
companionView,${diagnostics ? `
|
|
744
|
+
invitedEmail: invitedEmail.value,` : ''}
|
|
717
745
|
});
|
|
718
746
|
|
|
719
747
|
if (Array.isArray(result.projects)) {
|
|
@@ -777,7 +805,8 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform()) {
|
|
|
777
805
|
suggestedCount += 1;
|
|
778
806
|
}
|
|
779
807
|
|
|
780
|
-
document.querySelector('input[name="autoTrack"][value="' + (state.autoTrack === false ? 'no' : 'yes') + '"]').checked = true
|
|
808
|
+
document.querySelector('input[name="autoTrack"][value="' + (state.autoTrack === false ? 'no' : 'yes') + '"]').checked = true;${diagnostics ? `
|
|
809
|
+
invitedEmail.value = typeof state.invitedEmail === 'string' ? state.invitedEmail : '';` : ''}
|
|
781
810
|
document.querySelectorAll('.option-group').forEach(syncOptionGroup);
|
|
782
811
|
renderProjects();
|
|
783
812
|
|
|
@@ -826,7 +855,12 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform()) {
|
|
|
826
855
|
<div class="rail-item" data-step="projects" data-testid="setup-rail-projects">
|
|
827
856
|
<span class="rail-badge tech-box-red">02</span>
|
|
828
857
|
<span class="rail-label">PROJECTS</span>
|
|
829
|
-
</div
|
|
858
|
+
</div>${diagnostics ? `
|
|
859
|
+
<div class="step-connector" aria-hidden="true"></div>
|
|
860
|
+
<div class="rail-item" data-step="diagnostics" data-testid="setup-rail-diagnostics">
|
|
861
|
+
<span class="rail-badge tech-box-red">03</span>
|
|
862
|
+
<span class="rail-label">DIAGNOSTICS</span>
|
|
863
|
+
</div>` : ''}
|
|
830
864
|
</aside>
|
|
831
865
|
<div class="content">
|
|
832
866
|
<fieldset class="step" id="trackingStep" data-setup-step="tracking">
|
|
@@ -852,7 +886,13 @@ export function renderOnboardingWindow(platform = resolveSetupPlatform()) {
|
|
|
852
886
|
<button class="terminal-button tech-box-red" id="pickFolders" type="button" data-testid="add-projects">+ ADD PROJECTS</button>
|
|
853
887
|
<p class="hint">Existing files become the baseline.<br>Source and diffs stay local.</p>
|
|
854
888
|
</div>
|
|
855
|
-
</section
|
|
889
|
+
</section>${diagnostics ? `
|
|
890
|
+
<section class="step diagnostics-step" aria-labelledby="diagnosticsTitle" data-setup-step="diagnostics" data-testid="setup-diagnostics">
|
|
891
|
+
<h2 class="section-title" id="diagnosticsTitle">This test build sends diagnostic logs</h2>
|
|
892
|
+
<p>This test build of KitCode sends diagnostic logs to KitCode’s staging server: KitCode’s own events and errors, such as the tracker starting or a hook failing, so that a problem you meet can be traced without asking you for it. They never hold your code, your files, your prompts or your project names.</p>
|
|
893
|
+
<label for="invitedEmail">The email address you were invited with, optional: staff find your logs by it.</label>
|
|
894
|
+
<input id="invitedEmail" name="invitedEmail" type="email" autocomplete="email" maxlength="254" spellcheck="false" data-testid="invited-email">
|
|
895
|
+
</section>` : ''}
|
|
856
896
|
</div>
|
|
857
897
|
</div>
|
|
858
898
|
<div class="dashed-divider section-divider" aria-hidden="true">==================================================================================================================================================================================================</div>
|
package/src/runtime.mjs
CHANGED
|
@@ -787,8 +787,10 @@ function enqueue(runtime, projectId, run, job) {
|
|
|
787
787
|
}
|
|
788
788
|
|
|
789
789
|
// What is wrong with the tracker, by fixed code (the prompt hook turns each code into fixed words,
|
|
790
|
-
// so nothing from a file reaches the agent): the projects affected, and since when.
|
|
791
|
-
|
|
790
|
+
// so nothing from a file reaches the agent): the projects affected, and since when. `onProblem` hears of each project's
|
|
791
|
+
// problem raised and cleared, with the error that raised it where there is one: a tester build's diagnostics
|
|
792
|
+
// (src/diagnostics.mjs) record it, by the code, an opaque reference to the project and the error's code alone.
|
|
793
|
+
function reportProblem(runtime, code, projectId, message, error = null) {
|
|
792
794
|
const problem = runtime.problems.get(code) ?? {since: new Date().toISOString(), projects: new Set()};
|
|
793
795
|
|
|
794
796
|
if (problem.projects.has(projectId)) {
|
|
@@ -798,6 +800,7 @@ function reportProblem(runtime, code, projectId, message) {
|
|
|
798
800
|
problem.projects.add(projectId);
|
|
799
801
|
runtime.problems.set(code, problem);
|
|
800
802
|
runtime.log.warn(message);
|
|
803
|
+
runtime.watcherOptions?.onProblem?.({code, projectId, state: 'raised', error});
|
|
801
804
|
runtime.watcherOptions?.onStatus?.(trackerStatus(runtime));
|
|
802
805
|
}
|
|
803
806
|
|
|
@@ -813,6 +816,7 @@ function clearProblem(runtime, code, projectId) {
|
|
|
813
816
|
}
|
|
814
817
|
|
|
815
818
|
runtime.log.info(`${code} cleared for ${runtime.state.projects[projectId]?.repoRoot ?? projectId}`);
|
|
819
|
+
runtime.watcherOptions?.onProblem?.({code, projectId, state: 'cleared', error: null});
|
|
816
820
|
runtime.watcherOptions?.onStatus?.(trackerStatus(runtime));
|
|
817
821
|
}
|
|
818
822
|
|
|
@@ -837,7 +841,7 @@ function scanFailed(runtime, projectId, error) {
|
|
|
837
841
|
// stays as it is, and the next scan tries again.
|
|
838
842
|
const root = runtime.state.projects[projectId]?.repoRoot ?? projectId;
|
|
839
843
|
const missing = error?.code === 'ENOENT' || error?.code === 'ENOTDIR';
|
|
840
|
-
reportProblem(runtime, missing ? 'project-missing' : 'scan-failed', projectId, `Scanning ${root} failed (${error?.code ?? 'error'}: ${error?.message ?? error}); its snapshot is kept, and nothing in it is counted until a scan succeeds
|
|
844
|
+
reportProblem(runtime, missing ? 'project-missing' : 'scan-failed', projectId, `Scanning ${root} failed (${error?.code ?? 'error'}: ${error?.message ?? error}); its snapshot is kept, and nothing in it is counted until a scan succeeds.`, error);
|
|
841
845
|
}
|
|
842
846
|
|
|
843
847
|
// A Git project whose index stayed locked through a scan (index.lock, git.mjs gitArrival 'locked'): the
|
|
@@ -1021,12 +1025,12 @@ function watchFailed(runtime, projectId, run, error) {
|
|
|
1021
1025
|
const root = runtime.state.projects[projectId]?.repoRoot ?? projectId;
|
|
1022
1026
|
|
|
1023
1027
|
if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') {
|
|
1024
|
-
reportProblem(runtime, 'project-missing', projectId, `Watching ${root} failed (${error.code}: ${error.message}): the folder is gone, so nothing in it is counted
|
|
1028
|
+
reportProblem(runtime, 'project-missing', projectId, `Watching ${root} failed (${error.code}: ${error.message}): the folder is gone, so nothing in it is counted.`, error);
|
|
1025
1029
|
return;
|
|
1026
1030
|
}
|
|
1027
1031
|
|
|
1028
1032
|
run.poll = setInterval(() => queueFullScan(runtime, projectId, 'poll'), POLL_INTERVAL_MS);
|
|
1029
|
-
reportProblem(runtime, 'watch-failed', projectId, `Watching ${root} failed (${error?.code ?? 'error'}: ${error?.message ?? error}); polling it every ${POLL_INTERVAL_MS / 1000} s instead
|
|
1033
|
+
reportProblem(runtime, 'watch-failed', projectId, `Watching ${root} failed (${error?.code ?? 'error'}: ${error?.message ?? error}); polling it every ${POLL_INTERVAL_MS / 1000} s instead.`, error);
|
|
1030
1034
|
}
|
|
1031
1035
|
|
|
1032
1036
|
function watchProject(runtime, projectId, run) {
|
|
@@ -1271,7 +1275,8 @@ export function buildSummary(runtime) {
|
|
|
1271
1275
|
}
|
|
1272
1276
|
|
|
1273
1277
|
// Starts the tracker's work. `options.log` records what goes wrong; `options.onStatus` hears how the
|
|
1274
|
-
// tracker is doing whenever that changes, and after every save.
|
|
1278
|
+
// tracker is doing whenever that changes, and after every save; `options.onProblem`, each project's problem as it is raised
|
|
1279
|
+
// and cleared (reportProblem).
|
|
1275
1280
|
export function startWatchers(runtime, options = {}) {
|
|
1276
1281
|
runtime.watcherOptions = options;
|
|
1277
1282
|
runtime.log = options.log ?? SILENT_LOG;
|