kushi-agents 3.4.2 → 3.13.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/.github/copilot-instructions.kushi.md +38 -0
- package/README.md +33 -0
- package/bin/cli.mjs +2 -0
- package/package.json +17 -4
- package/plugin/agents/kushi.agent.md +155 -147
- package/plugin/instructions/ado-bootstrap-discovery.instructions.md +111 -0
- package/plugin/instructions/ado-engagement-tree.instructions.md +73 -0
- package/plugin/instructions/answer-from-evidence.instructions.md +1 -1
- package/plugin/instructions/auth-and-retry.instructions.md +51 -16
- package/plugin/instructions/azure-auth-patterns.instructions.md +13 -6
- package/plugin/instructions/bootstrap-status-format.instructions.md +113 -0
- package/plugin/instructions/capture-learnings.instructions.md +95 -0
- package/plugin/instructions/cleanup-on-resolution.instructions.md +69 -0
- package/plugin/instructions/crm-bootstrap-discovery.instructions.md +79 -0
- package/plugin/instructions/crm-internal-vs-confirmed.instructions.md +79 -0
- package/plugin/instructions/evidence-confidence-ladder.instructions.md +66 -0
- package/plugin/instructions/evidence-layout-canonical.instructions.md +115 -0
- package/plugin/instructions/evidence-thoroughness.instructions.md +82 -12
- package/plugin/instructions/full-view-gate.instructions.md +91 -0
- package/plugin/instructions/m365-id-registry.instructions.md +134 -0
- package/plugin/instructions/meetings-verbatim-required.instructions.md +176 -0
- package/plugin/instructions/run-reports.instructions.md +129 -0
- package/plugin/instructions/scope-boundaries.instructions.md +218 -0
- package/plugin/instructions/snapshot-vs-stream.instructions.md +2 -0
- package/plugin/instructions/update-ledger.instructions.md +132 -0
- package/plugin/instructions/verbatim-by-default.instructions.md +73 -0
- package/plugin/instructions/workiq-first.instructions.md +15 -31
- package/plugin/instructions/workiq-only.instructions.md +193 -0
- package/plugin/learnings/README.md +50 -0
- package/plugin/learnings/ado.md +45 -0
- package/plugin/learnings/crm.md +96 -0
- package/plugin/learnings/cross-cutting.md +36 -0
- package/plugin/learnings/email.md +33 -0
- package/plugin/learnings/meetings.md +30 -0
- package/plugin/learnings/misc.md +46 -0
- package/plugin/learnings/onenote.md +215 -0
- package/plugin/learnings/sharepoint.md +5 -0
- package/plugin/learnings/teams.md +5 -0
- package/plugin/plugin.json +22 -2
- package/plugin/prompts/apply-ado.prompt.md +14 -0
- package/plugin/prompts/propose-ado.prompt.md +12 -0
- package/plugin/reference-packs/fde/crm-field-manifest.md +165 -0
- package/plugin/skills/apply-ado-update/SKILL.md +125 -0
- package/plugin/skills/ask-project/SKILL.md +2 -0
- package/plugin/skills/bootstrap-project/SKILL.md +81 -3
- package/plugin/skills/propose-ado-update/SKILL.md +108 -0
- package/plugin/skills/pull-ado/SKILL.md +173 -23
- package/plugin/skills/pull-crm/SKILL.md +168 -15
- package/plugin/skills/pull-email/SKILL.md +139 -22
- package/plugin/skills/pull-meetings/SKILL.md +109 -25
- package/plugin/skills/pull-misc/README.md +84 -0
- package/plugin/skills/pull-misc/SKILL.md +257 -0
- package/plugin/skills/pull-misc/runner.mjs +280 -0
- package/plugin/skills/pull-onenote/README.md +90 -0
- package/plugin/skills/pull-onenote/SKILL.md +400 -51
- package/plugin/skills/pull-onenote/runner.mjs +356 -0
- package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +295 -0
- package/plugin/skills/pull-onenote/write-snapshot.mjs +271 -0
- package/plugin/skills/pull-sharepoint/SKILL.md +44 -12
- package/plugin/skills/pull-teams/SKILL.md +40 -11
- package/plugin/skills/refresh-project/SKILL.md +33 -2
- package/plugin/skills/self-check/run.ps1 +186 -4
- package/plugin/templates/ado-update/discussion-comment.template.md +26 -0
- package/plugin/templates/ado-update/integrations-ado-writes.example.yml +49 -0
- package/plugin/templates/ado-update/proposed.template.md +78 -0
- package/plugin/templates/init/external-links.template.txt +30 -0
- package/plugin/templates/init/project-integrations.template.yml +57 -2
- package/plugin/templates/snapshot/meeting-verbatim.template.md +110 -0
- package/plugin/templates/snapshot/meetings-series-index.template.md +3 -1
- package/plugin/templates/snapshot/onenote-page.template.md +92 -23
- package/plugin/templates/weekly/meetings-stream.template.md +11 -6
- package/src/copilot-instructions.mjs +80 -0
- package/src/main.mjs +18 -1
|
@@ -1,28 +1,97 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
page_title: "<page title>"
|
|
3
|
+
section: "<one_sectionName>"
|
|
4
|
+
notebook: "<notebookName>"
|
|
5
|
+
section_id: "<one_sectionFileId>"
|
|
6
|
+
wdpartid: "<wdpartid from enumerate>"
|
|
7
|
+
last_modified: "<as reported by WorkIQ>"
|
|
8
|
+
last_status: "captured | BODY-NOT-EXPOSED | user-pasted | enumeration-only"
|
|
9
|
+
attempts: <int>
|
|
10
|
+
last_attempt: "<ISO-8601>"
|
|
11
|
+
captured_at: "<ISO-8601 if captured/user-pasted, else empty>"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# OneNote Snapshot — Page: <page title>
|
|
15
|
+
|
|
16
|
+
- **Project:** <project>
|
|
17
|
+
- **Section:** <notebookName> / <one_sectionName> (`<one_sectionFileId>`)
|
|
18
|
+
- **Page wdpartid:** `<wdpartid>`
|
|
19
|
+
- **Last modified:** <as reported by WorkIQ>
|
|
20
|
+
- **Capture status:** `<last_status>`
|
|
21
|
+
- **Attempts:** <int>
|
|
22
|
+
- **Last attempt:** <ts>
|
|
23
|
+
- **Source basis:** WorkIQ enumerate + per-page verbatim probe (pull-onenote v2.5.0). Graph `/me/onenote/*` skipped per workspace policy. Per-page retry registry: `.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>.one_pages`.
|
|
24
|
+
|
|
25
|
+
<!-- IF last_status == "captured" or "user-pasted" -->
|
|
26
|
+
|
|
27
|
+
## AI Narrative Summary
|
|
28
|
+
|
|
29
|
+
<3+ paragraphs covering what the page is about, structure, key points, anything noteworthy.>
|
|
30
|
+
|
|
31
|
+
## Body (verbatim)
|
|
32
|
+
|
|
33
|
+
<full page body — NOT a summary, NOT a paraphrase. Reproduce content exactly as exposed by the enterprise retrieval surface, or exactly as the user pasted it.>
|
|
34
|
+
|
|
35
|
+
## Source Basis
|
|
36
|
+
|
|
37
|
+
- Tool used: WorkIQ (canonical Step B query) | user-paste
|
|
38
|
+
- Captured at: <ISO-8601>
|
|
39
|
+
|
|
40
|
+
<!-- ELSE IF last_status == "BODY-NOT-EXPOSED" -->
|
|
41
|
+
|
|
42
|
+
## Page body
|
|
43
|
+
|
|
44
|
+
❌ **page-body-unavailable: WorkIQ returned BODY-NOT-EXPOSED on attempt <N>.**
|
|
45
|
+
|
|
46
|
+
> Per `pull-onenote` v2.5.0 § "Empirical contract": WorkIQ OneNote body retrieval is non-deterministic — the same page may return a body on a later refresh. This page IS registered for retry in `.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>.one_pages` and will be re-attempted on every refresh until `last_status` becomes `captured` or `user-pasted`. Inferring the page contents from adjacent emails or chat traffic is forbidden.
|
|
47
|
+
|
|
48
|
+
### next_step
|
|
49
|
+
|
|
50
|
+
- (automatic) Next refresh will retry the verbatim body fetch (attempt <N+1>).
|
|
51
|
+
- (manual) Paste the verbatim page body to upgrade to `user-pasted`. Update the registry entry's `last_status` and `captured_at` accordingly, and append an AI Narrative Summary above the body.
|
|
52
|
+
|
|
53
|
+
### Page metadata
|
|
54
|
+
|
|
55
|
+
| Field | Value |
|
|
56
|
+
|---|---|
|
|
57
|
+
| Page title | `<title>` |
|
|
58
|
+
| Section | <notebook> / <section> |
|
|
59
|
+
| wdpartid | `<wdpartid>` |
|
|
60
|
+
| Last modified (per WorkIQ) | <date> |
|
|
61
|
+
| Capture status | `BODY-NOT-EXPOSED` |
|
|
62
|
+
| Attempts so far | <N> |
|
|
63
|
+
| Last attempt | <ts> |
|
|
64
|
+
|
|
65
|
+
## Source Basis
|
|
66
|
+
|
|
67
|
+
- Tool used: WorkIQ (Step A enumerate succeeded; Step B per-page probe returned BODY-NOT-EXPOSED).
|
|
68
|
+
- Graph `/me/onenote/*` not attempted — workspace policy blocks `Notes.Read.All` admin consent.
|
|
69
|
+
|
|
70
|
+
<!-- ELSE IF last_status == "enumeration-only" -->
|
|
71
|
+
|
|
72
|
+
## Page body
|
|
73
|
+
|
|
74
|
+
⚠️ **enumeration-only** — page enumerated in the section index, but no per-page body fetch attempted yet.
|
|
75
|
+
|
|
76
|
+
> Per `pull-onenote` v2.5.0: this is a transient state. The next refresh run will attempt Step B and either upgrade to `captured` or downgrade to `BODY-NOT-EXPOSED`.
|
|
77
|
+
|
|
78
|
+
### next_step
|
|
79
|
+
|
|
80
|
+
- (automatic) Next refresh will attempt the verbatim body fetch.
|
|
81
|
+
|
|
82
|
+
## Source Basis
|
|
83
|
+
|
|
84
|
+
- Tool used: WorkIQ Step A enumerate only. No Step B attempted in this run.
|
|
2
85
|
|
|
3
|
-
> **Snapshot.** Replaced on every refresh.
|
|
4
|
-
> Section: <section name>
|
|
5
|
-
> Notebook: <notebook name>
|
|
6
|
-
> Last modified: <YYYY-MM-DD HH:mm TZ> by <author>
|
|
7
|
-
> Last fetched: <YYYY-MM-DD HH:mm TZ>
|
|
8
|
-
|
|
9
|
-
## Source Basis
|
|
10
|
-
- Tool used: <WorkIQ | Graph (often 40001) | user-paste>
|
|
11
|
-
- Mutable hint: `one_sectionFileId` = <id>
|
|
12
|
-
|
|
13
|
-
## Body (verbatim)
|
|
14
|
-
|
|
15
|
-
<full page body — NOT a summary, NOT a paraphrase. Reproduce content as faithfully as the source allows.>
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
> Edits to this page during refresh windows are captured in `stream/<week>_onenote-stream.md`.
|
|
20
86
|
|
|
21
87
|
## Validation
|
|
22
88
|
|
|
23
|
-
|
|
89
|
+
Per `thoroughness-detector.instructions.md`, the writer must tick all that apply before saving:
|
|
24
90
|
|
|
25
|
-
- [ ]
|
|
26
|
-
- [ ]
|
|
27
|
-
- [ ]
|
|
28
|
-
- [ ]
|
|
91
|
+
- [ ] yaml front-matter present with `page_title`, `section`, `notebook`, `section_id`, `wdpartid`, `last_status`, `attempts`, `last_attempt`
|
|
92
|
+
- [ ] `last_status` is one of: `captured` | `BODY-NOT-EXPOSED` | `user-pasted` | `enumeration-only`
|
|
93
|
+
- [ ] If `last_status == captured` or `user-pasted`: AI Narrative Summary (3+ paragraphs) AND verbatim Body section are present and non-empty
|
|
94
|
+
- [ ] If `last_status == BODY-NOT-EXPOSED`: explicit marker, `next_step` block, and metadata table all present; NO inferred or paraphrased body content
|
|
95
|
+
- [ ] If `last_status == enumeration-only`: explicit transient marker present; NO body content of any kind
|
|
96
|
+
- [ ] Page is registered in `.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>.one_pages` with matching `last_status` and `attempts`
|
|
97
|
+
- [ ] No fabrication: nothing in this file came from adjacent emails, chats, or other sources to "fill in" what WorkIQ did not return
|
|
@@ -58,7 +58,8 @@ _2–4 sentence overview of meetings this week for this project: how many, key t
|
|
|
58
58
|
- **Actual Attendees:** {{PEOPLE}}
|
|
59
59
|
- **No-shows:** {{PEOPLE}}
|
|
60
60
|
- **Purpose / Stated Agenda:** _one or two paragraphs — what this meeting was for, what was on the agenda, what was the framing._
|
|
61
|
-
- **Source basis:** _transcript (
|
|
61
|
+
- **Source basis:** _transcript (raw VTT) | transcript (WorkIQ summary, not raw) | reconstructed from meeting chat (no transcript) | partial — chat + recap only | ❌ source-expired-or-unrecoverable._
|
|
62
|
+
- **Verbatim folder (REQUIRED):** `Evidence/<alias>/meetings/verbatim/<YYYY-MM-DD-HHMM>_<slug>/` — raw immutable capture (chat-messages.json/md, transcript.vtt or transcript-source.md, recording-url.txt, attachments/, coverage.md). All assertions below cite files inside this folder per `meetings-verbatim-required.instructions.md`.
|
|
62
63
|
|
|
63
64
|
#### Detailed Discussion Summary
|
|
64
65
|
_Multi-paragraph narrative of what was actually discussed end-to-end. Do NOT compress to 3 bullets. Include who said what, what context emerged, what was challenged, what was confirmed. This is the section that lets a State-file reader reconstruct the meeting without watching the recording._
|
|
@@ -99,10 +100,11 @@ _Every substantive exchange. Collapse only routine acks ("got it", "thanks"). Fo
|
|
|
99
100
|
- _e.g._ Customer requested executive sponsor reciprocal commitment from Microsoft.
|
|
100
101
|
|
|
101
102
|
#### Artifacts
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
103
|
+
- **Verbatim folder:** `Evidence/<alias>/meetings/verbatim/{{YYYY-MM-DD-HHMM}}_{{slug}}/`
|
|
104
|
+
- Recording (live URL — expires): {{URL}} _(also persisted in verbatim/recording-url.txt)_
|
|
105
|
+
- Transcript: {{URL}} _(raw VTT in verbatim/transcript.vtt if available)_
|
|
106
|
+
- Copilot recap: {{URL}} _(in verbatim/recap-card.md)_
|
|
107
|
+
- Shared files / decks / docs: {{URL list}} _(downloaded copies in verbatim/attachments/)_
|
|
106
108
|
|
|
107
109
|
#### Coverage Notes
|
|
108
110
|
- _What was retrievable vs what wasn't_ — e.g. "Transcript not generated; reconstructed from 47-message chat thread + Copilot recap card. Decisions and Action Items high confidence; Transcript Walk-Through derived from chat exchange timestamps."
|
|
@@ -179,4 +181,7 @@ _Generated from Teams meetings (calendar + transcripts + recordings + Copilot re
|
|
|
179
181
|
- [ ] action-items-have-owner-and-due
|
|
180
182
|
- [ ] transcript-walk-through-present
|
|
181
183
|
- [ ] no-link-only-stubs
|
|
182
|
-
- [ ] coverage-notes-explain-any-gaps
|
|
184
|
+
- [ ] coverage-notes-explain-any-gaps
|
|
185
|
+
- [ ] verbatim-folder-exists-per-meeting (REQUIRED v3.10.0 — sibling `verbatim/<YYYY-MM-DD-HHMM>_<slug>/` dir per meeting block, enforced by self-check D13.a)
|
|
186
|
+
- [ ] verbatim-folder-non-empty (at least chat-messages.json OR transcript.* OR transcript-source.md beyond captured-at.txt — D13.b)
|
|
187
|
+
- [ ] transcript-class-file-present (REQUIRED v3.10.1 — `transcript.vtt` OR `transcript.txt` OR `transcript-source.md` non-empty; chat alone is NOT a transcript — D13.c). Run the exhaustive cascade per `meetings-verbatim-required.instructions.md`: `m365_get_transcript` → Graph REST `/onlineMeetings/.../transcripts/.../content` → `m365_get_facilitator_notes` → WorkIQ strict full-text pull → recording download.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const KUSHI_BEGIN_MARKER = '<!-- BEGIN kushi-agents (managed — do not edit between markers) -->';
|
|
5
|
+
export const KUSHI_END_MARKER = '<!-- END kushi-agents -->';
|
|
6
|
+
|
|
7
|
+
const MARKER_REGEX = new RegExp(
|
|
8
|
+
`${escapeRegex(KUSHI_BEGIN_MARKER)}[\\s\\S]*?${escapeRegex(KUSHI_END_MARKER)}`,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
function escapeRegex(str) {
|
|
12
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Wrap the source content in our managed-block markers.
|
|
17
|
+
* @param {string} source
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
function buildBlock(source) {
|
|
21
|
+
return `${KUSHI_BEGIN_MARKER}\n${source.trim()}\n${KUSHI_END_MARKER}\n`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Merge the Kushi section into .github/copilot-instructions.md using
|
|
26
|
+
* managed-block markers. Three behaviors:
|
|
27
|
+
*
|
|
28
|
+
* - File missing → create it with a brief header + our block
|
|
29
|
+
* - File has markers → replace block content in place (idempotent upgrade)
|
|
30
|
+
* - File without markers → append our block at the end, preserving user content
|
|
31
|
+
*
|
|
32
|
+
* @param {string} projectRoot
|
|
33
|
+
* @param {string} sourcePkgDir – root of the npm package (for reading the source template)
|
|
34
|
+
* @returns {{ action: 'created' | 'updated-block' | 'appended-block' | 'unchanged', path: string }}
|
|
35
|
+
*/
|
|
36
|
+
export function mergeCopilotInstructions(projectRoot, sourcePkgDir) {
|
|
37
|
+
const srcTemplate = path.join(
|
|
38
|
+
sourcePkgDir,
|
|
39
|
+
'.github',
|
|
40
|
+
'copilot-instructions.kushi.md',
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(srcTemplate)) {
|
|
44
|
+
return { action: 'unchanged', path: '', reason: 'source template missing' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sourceContent = fs.readFileSync(srcTemplate, 'utf-8');
|
|
48
|
+
const block = buildBlock(sourceContent);
|
|
49
|
+
|
|
50
|
+
const githubDir = path.join(projectRoot, '.github');
|
|
51
|
+
const targetPath = path.join(githubDir, 'copilot-instructions.md');
|
|
52
|
+
const displayPath = '.github/copilot-instructions.md';
|
|
53
|
+
|
|
54
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
55
|
+
|
|
56
|
+
if (!fs.existsSync(targetPath)) {
|
|
57
|
+
const header = `# Copilot Instructions\n\n_This file is auto-loaded by GitHub Copilot Chat into every chat turn in this workspace._\n\n`;
|
|
58
|
+
fs.writeFileSync(targetPath, header + block, 'utf-8');
|
|
59
|
+
return { action: 'created', path: displayPath };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const existing = fs.readFileSync(targetPath, 'utf-8');
|
|
63
|
+
|
|
64
|
+
if (MARKER_REGEX.test(existing)) {
|
|
65
|
+
const updated = existing.replace(MARKER_REGEX, block.trimEnd());
|
|
66
|
+
if (updated === existing) {
|
|
67
|
+
return { action: 'unchanged', path: displayPath };
|
|
68
|
+
}
|
|
69
|
+
fs.writeFileSync(targetPath, updated, 'utf-8');
|
|
70
|
+
return { action: 'updated-block', path: displayPath };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const separator = existing.endsWith('\n\n')
|
|
74
|
+
? ''
|
|
75
|
+
: existing.endsWith('\n')
|
|
76
|
+
? '\n'
|
|
77
|
+
: '\n\n';
|
|
78
|
+
fs.writeFileSync(targetPath, existing + separator + block, 'utf-8');
|
|
79
|
+
return { action: 'appended-block', path: displayPath };
|
|
80
|
+
}
|
package/src/main.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { promptForDestination } from './prompt.mjs';
|
|
15
15
|
import { copyAssets, copyProjectFiles } from './copy-assets.mjs';
|
|
16
16
|
import { mergeSettings } from './settings.mjs';
|
|
17
|
+
import { mergeCopilotInstructions } from './copilot-instructions.mjs';
|
|
17
18
|
import {
|
|
18
19
|
resolveProfile,
|
|
19
20
|
makeIncludeFilter,
|
|
@@ -48,7 +49,7 @@ function resolveProfileForInstall(profileName) {
|
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* Orchestrator: prompt -> copy -> settings -> summary.
|
|
51
|
-
* @param {{ dest?: string, force?: boolean, noSettings?: boolean, target?: string, profile?: string }} options
|
|
52
|
+
* @param {{ dest?: string, force?: boolean, noSettings?: boolean, noInstructions?: boolean, target?: string, profile?: string }} options
|
|
52
53
|
*/
|
|
53
54
|
export async function main(options = {}) {
|
|
54
55
|
const version = getVersion();
|
|
@@ -150,6 +151,22 @@ async function installVscode(options, resolved, version) {
|
|
|
150
151
|
console.log('\n Skipped .vscode/settings.json (--no-settings)');
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
if (!options.noInstructions) {
|
|
155
|
+
const result = mergeCopilotInstructions(projectRoot, PKG_ROOT);
|
|
156
|
+
const labelByAction = {
|
|
157
|
+
'created': 'Created',
|
|
158
|
+
'updated-block': 'Updated managed Kushi block in',
|
|
159
|
+
'appended-block': 'Appended managed Kushi block to existing',
|
|
160
|
+
'unchanged': 'Unchanged',
|
|
161
|
+
};
|
|
162
|
+
if (result.action !== 'unchanged' || result.path) {
|
|
163
|
+
const label = labelByAction[result.action] || 'Touched';
|
|
164
|
+
console.log(`\n ${label} ${result.path}`);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
console.log('\n Skipped .github/copilot-instructions.md (--no-instructions)');
|
|
168
|
+
}
|
|
169
|
+
|
|
153
170
|
const { copied: projCopied, skipped: projSkipped } = copyProjectFiles(PKG_ROOT);
|
|
154
171
|
if (projCopied.length > 0) {
|
|
155
172
|
console.log('\n Copied project files to .github/');
|